summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.ci/scripts/android/build.sh15
-rwxr-xr-x.ci/scripts/android/upload.sh27
-rw-r--r--.codespellrc4
-rw-r--r--.github/workflows/verify.yml43
-rw-r--r--.gitmodules3
-rw-r--r--.reuse/dep512
-rw-r--r--CMakeLists.txt69
-rw-r--r--CMakeModules/DownloadExternals.cmake6
-rw-r--r--LICENSES/MPL-2.0.txt373
-rw-r--r--externals/CMakeLists.txt6
-rw-r--r--externals/ffmpeg/CMakeLists.txt70
m---------externals/libadrenotools0
-rw-r--r--src/CMakeLists.txt11
-rw-r--r--src/android/.gitignore65
-rw-r--r--src/android/app/build.gradle.kts258
-rw-r--r--src/android/app/proguard-rules.pro24
-rw-r--r--src/android/app/src/ea/res/drawable/ic_yuzu.xml22
-rw-r--r--src/android/app/src/ea/res/drawable/ic_yuzu_full.xml12
-rw-r--r--src/android/app/src/ea/res/drawable/ic_yuzu_title.xml24
-rw-r--r--src/android/app/src/main/AndroidManifest.xml86
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt523
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt61
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt306
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt134
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt69
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt54
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt70
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt121
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/ui/KeyboardDialogFragment.kt100
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt48
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt101
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt302
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt136
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt158
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt39
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt40
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt64
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt58
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt243
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt84
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt57
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt340
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt122
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt474
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt58
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt48
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt60
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt39
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt35
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt48
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt241
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt125
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt83
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt613
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt340
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt210
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt70
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicenseBottomSheetDialogFragment.kt59
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt137
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/PermissionDeniedDialogFragment.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ResetSettingsDialogFragment.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt230
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt329
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupWarningDialogFragment.kt86
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt61
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt48
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt118
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/License.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MinimalDocumentFile.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt47
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt1066
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt148
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt274
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt282
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt165
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt528
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/ThemeProvider.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt68
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt112
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt68
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt350
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt70
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt98
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt152
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt47
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt360
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt40
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt168
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt40
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt97
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt48
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt28
-rw-r--r--src/android/app/src/main/jni/android_common/android_common.cpp35
-rw-r--r--src/android/app/src/main/jni/android_common/android_common.h12
-rw-r--r--src/android/app/src/main/jni/applets/software_keyboard.cpp277
-rw-r--r--src/android/app/src/main/jni/applets/software_keyboard.h78
-rw-r--r--src/android/app/src/main/jni/config.cpp301
-rw-r--r--src/android/app/src/main/jni/config.h37
-rw-r--r--src/android/app/src/main/jni/default_ini.h511
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.cpp79
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.h64
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp116
-rw-r--r--src/android/app/src/main/jni/id_cache.h19
-rw-r--r--src/android/app/src/main/jni/native.cpp850
-rw-r--r--src/android/app/src/main/jni/native.h165
-rw-r--r--src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml16
-rw-r--r--src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_settings_fragment_in.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_settings_fragment_out.xml10
-rw-r--r--src/android/app/src/main/res/animator/menu_slide_in_from_start.xml20
-rw-r--r--src/android/app/src/main/res/animator/menu_slide_out_to_start.xml21
-rw-r--r--src/android/app/src/main/res/drawable-hdpi/ic_stat_notification_logo.pngbin0 -> 46179 bytes
-rw-r--r--src/android/app/src/main/res/drawable-xhdpi/ic_stat_notification_logo.pngbin0 -> 48264 bytes
-rw-r--r--src/android/app/src/main/res/drawable-xhdpi/tv_banner.pngbin0 -> 7764 bytes
-rw-r--r--src/android/app/src/main/res/drawable-xxhdpi/ic_stat_notification_logo.pngbin0 -> 56651 bytes
-rw-r--r--src/android/app/src/main/res/drawable/default_icon.jpgbin0 -> 6285 bytes
-rw-r--r--src/android/app/src/main/res/drawable/dpad_standard.xml24
-rw-r--r--src/android/app/src/main/res/drawable/dpad_standard_cardinal_depressed.xml24
-rw-r--r--src/android/app/src/main/res/drawable/dpad_standard_diagonal_depressed.xml24
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_a.xml22
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_a_depressed.xml8
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_b.xml22
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_b_depressed.xml8
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_home.xml21
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_home_depressed.xml8
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_minus.xml22
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_minus_depressed.xml9
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_plus.xml22
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_plus_depressed.xml9
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_screenshot.xml21
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_screenshot_depressed.xml8
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_x.xml22
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_x_depressed.xml8
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_y.xml22
-rw-r--r--src/android/app/src/main/res/drawable/facebutton_y_depressed.xml8
-rw-r--r--src/android/app/src/main/res/drawable/ic_add.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_arrow_forward.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_back.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_cartridge.xml12
-rw-r--r--src/android/app/src/main/res/drawable/ic_cartridge_outline.xml12
-rw-r--r--src/android/app/src/main/res/drawable/ic_check.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_check_circle.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_clear.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_controller.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_diamond.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_discord.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_exit.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_firmware.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_folder_open.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_github.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_icon_bg.xml751
-rw-r--r--src/android/app/src/main/res/drawable/ic_info_outline.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_install.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_key.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_launcher.xml6
-rw-r--r--src/android/app/src/main/res/drawable/ic_log.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_nfc.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_notification.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_options.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_palette.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_pause.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_play.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_save.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_search.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_settings.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_settings_outline.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_system_update_alt.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_unlock.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_website.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_yuzu.xml22
-rw-r--r--src/android/app/src/main/res/drawable/ic_yuzu_full.xml12
-rw-r--r--src/android/app/src/main/res/drawable/ic_yuzu_title.xml24
-rw-r--r--src/android/app/src/main/res/drawable/joystick.xml45
-rw-r--r--src/android/app/src/main/res/drawable/joystick_depressed.xml10
-rw-r--r--src/android/app/src/main/res/drawable/joystick_range.xml38
-rw-r--r--src/android/app/src/main/res/drawable/l_shoulder.xml23
-rw-r--r--src/android/app/src/main/res/drawable/l_shoulder_depressed.xml8
-rw-r--r--src/android/app/src/main/res/drawable/premium_background.xml9
-rw-r--r--src/android/app/src/main/res/drawable/r_shoulder.xml23
-rw-r--r--src/android/app/src/main/res/drawable/r_shoulder_depressed.xml8
-rw-r--r--src/android/app/src/main/res/drawable/selector_cartridge.xml5
-rw-r--r--src/android/app/src/main/res/drawable/selector_settings.xml5
-rw-r--r--src/android/app/src/main/res/drawable/zl_trigger.xml25
-rw-r--r--src/android/app/src/main/res/drawable/zl_trigger_depressed.xml10
-rw-r--r--src/android/app/src/main/res/drawable/zr_trigger.xml25
-rw-r--r--src/android/app/src/main/res/drawable/zr_trigger_depressed.xml10
-rw-r--r--src/android/app/src/main/res/layout-w600dp/activity_main.xml58
-rw-r--r--src/android/app/src/main/res/layout-w600dp/fragment_setup.xml40
-rw-r--r--src/android/app/src/main/res/layout-w600dp/page_setup.xml65
-rw-r--r--src/android/app/src/main/res/layout/activity_emulation.xml13
-rw-r--r--src/android/app/src/main/res/layout/activity_main.xml58
-rw-r--r--src/android/app/src/main/res/layout/activity_settings.xml50
-rw-r--r--src/android/app/src/main/res/layout/card_game.xml67
-rw-r--r--src/android/app/src/main/res/layout/card_home_option.xml60
-rw-r--r--src/android/app/src/main/res/layout/dialog_edit_text.xml23
-rw-r--r--src/android/app/src/main/res/layout/dialog_license.xml64
-rw-r--r--src/android/app/src/main/res/layout/dialog_overlay_adjust.xml67
-rw-r--r--src/android/app/src/main/res/layout/dialog_progress_bar.xml24
-rw-r--r--src/android/app/src/main/res/layout/dialog_slider.xml37
-rw-r--r--src/android/app/src/main/res/layout/fragment_about.xml232
-rw-r--r--src/android/app/src/main/res/layout/fragment_early_access.xml242
-rw-r--r--src/android/app/src/main/res/layout/fragment_emulation.xml70
-rw-r--r--src/android/app/src/main/res/layout/fragment_games.xml34
-rw-r--r--src/android/app/src/main/res/layout/fragment_home_settings.xml34
-rw-r--r--src/android/app/src/main/res/layout/fragment_licenses.xml30
-rw-r--r--src/android/app/src/main/res/layout/fragment_search.xml183
-rw-r--r--src/android/app/src/main/res/layout/fragment_settings.xml14
-rw-r--r--src/android/app/src/main/res/layout/fragment_setup.xml42
-rw-r--r--src/android/app/src/main/res/layout/header_in_game.xml14
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting.xml41
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting_switch.xml50
-rw-r--r--src/android/app/src/main/res/layout/list_item_settings_header.xml20
-rw-r--r--src/android/app/src/main/res/layout/page_setup.xml72
-rw-r--r--src/android/app/src/main/res/menu-w600dp/menu_navigation.xml19
-rw-r--r--src/android/app/src/main/res/menu/menu_in_game.xml24
-rw-r--r--src/android/app/src/main/res/menu/menu_navigation.xml19
-rw-r--r--src/android/app/src/main/res/menu/menu_overlay_options.xml45
-rw-r--r--src/android/app/src/main/res/menu/menu_settings.xml2
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml59
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml332
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml335
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-night-v31/themes.xml31
-rw-r--r--src/android/app/src/main/res/values-night/themes.xml9
-rw-r--r--src/android/app/src/main/res/values-night/yuzu_colors.xml37
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-v31/themes.xml31
-rw-r--r--src/android/app/src/main/res/values-w600dp/bools.xml4
-rw-r--r--src/android/app/src/main/res/values-w600dp/dimens.xml5
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml337
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml336
-rw-r--r--src/android/app/src/main/res/values/arrays.xml227
-rw-r--r--src/android/app/src/main/res/values/bools.xml4
-rw-r--r--src/android/app/src/main/res/values/dimens.xml18
-rw-r--r--src/android/app/src/main/res/values/integers.xml37
-rw-r--r--src/android/app/src/main/res/values/strings.xml874
-rw-r--r--src/android/app/src/main/res/values/styles.xml36
-rw-r--r--src/android/app/src/main/res/values/themes.xml51
-rw-r--r--src/android/app/src/main/res/values/yuzu_colors.xml37
-rw-r--r--src/android/app/src/main/res/xml/data_extraction_rules.xml20
-rw-r--r--src/android/app/src/main/res/xml/data_extraction_rules_api_31.xml43
-rw-r--r--src/android/app/src/main/res/xml/locales_config.xml17
-rw-r--r--src/android/app/src/main/res/xml/nfc_tech_filter.xml6
-rw-r--r--src/android/build.gradle.kts13
-rw-r--r--src/android/gradle.properties17
-rw-r--r--src/android/gradle/wrapper/gradle-wrapper.jarbin0 -> 54708 bytes
-rw-r--r--src/android/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xsrc/android/gradlew175
-rw-r--r--src/android/gradlew.bat87
-rw-r--r--src/android/settings.gradle.kts21
-rw-r--r--src/audio_core/audio_core.cpp8
-rw-r--r--src/audio_core/audio_core.h14
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp8
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h4
-rw-r--r--src/audio_core/sink/sink_stream.cpp5
-rw-r--r--src/audio_core/sink/sink_stream.h5
-rw-r--r--src/common/CMakeLists.txt13
-rw-r--r--src/common/dynamic_library.cpp2
-rw-r--r--src/common/dynamic_library.h3
-rw-r--r--src/common/error.cpp3
-rw-r--r--src/common/fs/file.cpp38
-rw-r--r--src/common/fs/fs_android.cpp98
-rw-r--r--src/common/fs/fs_android.h62
-rw-r--r--src/common/fs/fs_paths.h1
-rw-r--r--src/common/fs/path_util.cpp27
-rw-r--r--src/common/fs/path_util.h9
-rw-r--r--src/common/host_memory.cpp12
-rw-r--r--src/common/logging/backend.cpp26
-rw-r--r--src/common/logging/text_formatter.cpp35
-rw-r--r--src/common/logging/text_formatter.h2
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/common/settings.h1
-rw-r--r--src/common/uuid.cpp2
-rw-r--r--src/core/CMakeLists.txt10
-rw-r--r--src/core/arm/arm_interface.cpp84
-rw-r--r--src/core/arm/arm_interface.h37
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h29
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp64
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.h16
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp64
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h18
-rw-r--r--src/core/arm/dynarmic/dynarmic_cp15.cpp (renamed from src/core/arm/dynarmic/arm_dynarmic_cp15.cpp)2
-rw-r--r--src/core/arm/dynarmic/dynarmic_cp15.h (renamed from src/core/arm/dynarmic/arm_dynarmic_cp15.h)0
-rw-r--r--src/core/arm/dynarmic/dynarmic_exclusive_monitor.cpp (renamed from src/core/arm/dynarmic/arm_exclusive_monitor.cpp)2
-rw-r--r--src/core/arm/dynarmic/dynarmic_exclusive_monitor.h (renamed from src/core/arm/dynarmic/arm_exclusive_monitor.h)0
-rw-r--r--src/core/arm/exclusive_monitor.cpp2
-rw-r--r--src/core/core.cpp44
-rw-r--r--src/core/core.h11
-rw-r--r--src/core/crypto/key_manager.cpp8
-rw-r--r--src/core/crypto/key_manager.h3
-rw-r--r--src/core/device_memory.cpp8
-rw-r--r--src/core/file_sys/control_metadata.cpp12
-rw-r--r--src/core/file_sys/control_metadata.h4
-rw-r--r--src/core/file_sys/patch_manager.cpp33
-rw-r--r--src/core/file_sys/submission_package.h1
-rw-r--r--src/core/frontend/emu_window.cpp2
-rw-r--r--src/core/frontend/emu_window.h48
-rw-r--r--src/core/frontend/graphics_context.h62
-rw-r--r--src/core/hid/emulated_console.cpp32
-rw-r--r--src/core/hid/emulated_console.h4
-rw-r--r--src/core/hid/emulated_controller.cpp26
-rw-r--r--src/core/hid/emulated_controller.h2
-rw-r--r--src/core/hle/kernel/k_address_space_info.cpp5
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp1
-rw-r--r--src/core/hle/service/nfc/common/device.cpp160
-rw-r--r--src/core/hle/service/nfc/common/device.h10
-rw-r--r--src/core/hle/service/nfc/common/device_manager.cpp14
-rw-r--r--src/core/hle/service/nfc/nfc_interface.cpp8
-rw-r--r--src/core/hle/service/nfc/nfc_result.h20
-rw-r--r--src/core/hle/service/nfp/nfp_interface.cpp6
-rw-r--r--src/core/hle/service/nfp/nfp_result.h2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp4
-rw-r--r--src/core/loader/nro.cpp13
-rw-r--r--src/core/loader/nro.h2
-rw-r--r--src/input_common/drivers/virtual_amiibo.cpp34
-rw-r--r--src/input_common/drivers/virtual_amiibo.h2
-rw-r--r--src/input_common/drivers/virtual_gamepad.cpp16
-rw-r--r--src/input_common/drivers/virtual_gamepad.h12
-rw-r--r--src/shader_recompiler/CMakeLists.txt2
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp10
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp44
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp17
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp61
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h16
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.cpp6
-rw-r--r--src/shader_recompiler/host_translate_info.h3
-rw-r--r--src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp44
-rw-r--r--src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp185
-rw-r--r--src/shader_recompiler/ir_opt/passes.h2
-rw-r--r--src/shader_recompiler/profile.h3
-rw-r--r--src/shader_recompiler/runtime_info.h2
-rw-r--r--src/video_core/CMakeLists.txt10
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h92
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h12
-rw-r--r--src/video_core/engines/maxwell_3d.cpp12
-rw-r--r--src/video_core/gpu.cpp1
-rw-r--r--src/video_core/gpu_thread.cpp2
-rw-r--r--src/video_core/renderer_base.cpp1
-rw-r--r--src/video_core/renderer_base.h5
-rw-r--r--src/video_core/renderer_null/renderer_null.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp76
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h33
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_device.h5
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_shader_context.h1
-rw-r--r--src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp150
-rw-r--r--src/video_core/renderer_opengl/gl_staging_buffer_pool.h (renamed from src/video_core/renderer_opengl/gl_stream_buffer.h)44
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.cpp63
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp87
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h47
-rw-r--r--src/video_core/renderer_opengl/util_shaders.cpp9
-rw-r--r--src/video_core/renderer_opengl/util_shaders.h10
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp8
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp9
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp64
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.cpp52
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.h15
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_turbo_mode.cpp21
-rw-r--r--src/video_core/renderer_vulkan/vk_turbo_mode.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.h2
-rw-r--r--src/video_core/texture_cache/image_info.cpp20
-rw-r--r--src/video_core/texture_cache/texture_cache.h12
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.cpp28
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp105
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h32
-rw-r--r--src/video_core/vulkan_common/vulkan_library.cpp18
-rw-r--r--src/video_core/vulkan_common/vulkan_library.h6
-rw-r--r--src/yuzu/bootmanager.cpp1
-rw-r--r--src/yuzu/configuration/config.cpp8
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp4
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp8
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui10
-rw-r--r--src/yuzu/main.cpp13
-rw-r--r--src/yuzu/startup_checks.cpp4
-rw-r--r--src/yuzu_cmd/default_ini.h2
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h2
425 files changed, 27873 insertions, 704 deletions
diff --git a/.ci/scripts/android/build.sh b/.ci/scripts/android/build.sh
new file mode 100755
index 000000000..a5fd1ee18
--- /dev/null
+++ b/.ci/scripts/android/build.sh
@@ -0,0 +1,15 @@
1#!/bin/bash -ex
2
3# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
4# SPDX-License-Identifier: GPL-3.0-or-later
5
6export NDK_CCACHE="$(which ccache)"
7ccache -s
8
9BUILD_FLAVOR=mainline
10
11cd src/android
12chmod +x ./gradlew
13./gradlew "assemble${BUILD_FLAVOR}Release" "bundle${BUILD_FLAVOR}Release"
14
15ccache -s
diff --git a/.ci/scripts/android/upload.sh b/.ci/scripts/android/upload.sh
new file mode 100755
index 000000000..cfaeff328
--- /dev/null
+++ b/.ci/scripts/android/upload.sh
@@ -0,0 +1,27 @@
1#!/bin/bash -ex
2
3# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
4# SPDX-License-Identifier: GPL-3.0-or-later
5
6. ./.ci/scripts/common/pre-upload.sh
7
8REV_NAME="yuzu-${GITDATE}-${GITREV}"
9
10BUILD_FLAVOR=mainline
11
12cp src/android/app/build/outputs/apk/"${BUILD_FLAVOR}/release/app-${BUILD_FLAVOR}-release.apk" \
13 "artifacts/${REV_NAME}.apk"
14cp src/android/app/build/outputs/bundle/"${BUILD_FLAVOR}Release"/"app-${BUILD_FLAVOR}-release.aab" \
15 "artifacts/${REV_NAME}.aab"
16
17if [ -n "${ANDROID_KEYSTORE_B64}" ]
18then
19 echo "Signing apk..."
20 base64 --decode <<< "${ANDROID_KEYSTORE_B64}" > ks.jks
21
22 apksigner sign --ks ks.jks \
23 --ks-key-alias "${ANDROID_KEY_ALIAS}" \
24 --ks-pass env:ANDROID_KEYSTORE_PASS "artifacts/${REV_NAME}.apk"
25else
26 echo "No keystore specified, not signing the APK files."
27fi
diff --git a/.codespellrc b/.codespellrc
index 786a991eb..01ddd2362 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -2,5 +2,5 @@
2; SPDX-License-Identifier: GPL-2.0-or-later 2; SPDX-License-Identifier: GPL-2.0-or-later
3 3
4[codespell] 4[codespell]
5skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES 5skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res
6ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink 6ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index 7cde8380b..bd4141f56 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -122,3 +122,46 @@ jobs:
122 with: 122 with:
123 name: ${{ env.INDIVIDUAL_EXE }} 123 name: ${{ env.INDIVIDUAL_EXE }}
124 path: ${{ env.INDIVIDUAL_EXE }} 124 path: ${{ env.INDIVIDUAL_EXE }}
125 android:
126 runs-on: ubuntu-latest
127 needs: format
128 steps:
129 - uses: actions/checkout@v3
130 with:
131 submodules: recursive
132 - name: set up JDK 17
133 uses: actions/setup-java@v3
134 with:
135 java-version: '17'
136 distribution: 'adopt'
137 - name: Set up cache
138 uses: actions/cache@v3
139 with:
140 path: |
141 ~/.gradle/caches
142 ~/.gradle/wrapper
143 ~/.ccache
144 key: ${{ runner.os }}-android-${{ github.sha }}
145 restore-keys: |
146 ${{ runner.os }}-android-
147 - name: Query tag name
148 uses: olegtarasov/get-tag@v2.1.2
149 id: tagName
150 - name: Install dependencies
151 run: |
152 sudo apt-get update
153 sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
154 git -C ./externals/vcpkg/ fetch --all --unshallow
155 - name: Build
156 run: ./.ci/scripts/android/build.sh
157 - name: Copy and sign artifacts
158 env:
159 ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
160 ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
161 ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
162 run: ./.ci/scripts/android/upload.sh
163 - name: Upload
164 uses: actions/upload-artifact@v3
165 with:
166 name: android
167 path: artifacts/
diff --git a/.gitmodules b/.gitmodules
index 75c7b5fe0..95eae8109 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -49,3 +49,6 @@
49[submodule "cpp-jwt"] 49[submodule "cpp-jwt"]
50 path = externals/cpp-jwt 50 path = externals/cpp-jwt
51 url = https://github.com/arun11299/cpp-jwt.git 51 url = https://github.com/arun11299/cpp-jwt.git
52[submodule "libadrenotools"]
53 path = externals/libadrenotools
54 url = https://github.com/bylaws/libadrenotools
diff --git a/.reuse/dep5 b/.reuse/dep5
index 3810f2c41..31178fc4c 100644
--- a/.reuse/dep5
+++ b/.reuse/dep5
@@ -135,3 +135,15 @@ License: GPL-3.0-or-later
135Files: .github/ISSUE_TEMPLATE/* 135Files: .github/ISSUE_TEMPLATE/*
136Copyright: 2022 yuzu Emulator Project 136Copyright: 2022 yuzu Emulator Project
137License: GPL-2.0-or-later 137License: GPL-2.0-or-later
138
139Files: src/android/app/src/ea/res/*
140Copyright: 2023 yuzu Emulator Project
141License: GPL-3.0-or-later
142
143Files: src/android/app/src/main/res/*
144Copyright: 2023 yuzu Emulator Project
145License: GPL-3.0-or-later
146
147Files: src/android/gradle/wrapper/*
148Copyright: 2023 yuzu Emulator Project
149License: GPL-3.0-or-later
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7276ac9dd..3d03bbf94 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,6 +11,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modul
11include(DownloadExternals) 11include(DownloadExternals)
12include(CMakeDependentOption) 12include(CMakeDependentOption)
13include(CTest) 13include(CTest)
14include(FetchContent)
14 15
15# Set bundled sdl2/qt as dependent options. 16# Set bundled sdl2/qt as dependent options.
16# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON 17# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
@@ -19,7 +20,7 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON
19# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion 20# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion
20CMAKE_DEPENDENT_OPTION(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" ON "ENABLE_SDL2;NOT MSVC" OFF) 21CMAKE_DEPENDENT_OPTION(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" ON "ENABLE_SDL2;NOT MSVC" OFF)
21 22
22option(ENABLE_LIBUSB "Enable the use of LibUSB" ON) 23cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" OFF)
23 24
24option(ENABLE_OPENGL "Enable OpenGL" ON) 25option(ENABLE_OPENGL "Enable OpenGL" ON)
25mark_as_advanced(FORCE ENABLE_OPENGL) 26mark_as_advanced(FORCE ENABLE_OPENGL)
@@ -48,7 +49,7 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
48 49
49option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) 50option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
50 51
51option(YUZU_ROOM "Compile LDN room server" ON) 52cmake_dependent_option(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
52 53
53CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) 54CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
54 55
@@ -60,7 +61,67 @@ option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
60 61
61CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) 62CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
62 63
64# On Android, fetch and compile libcxx before doing anything else
65if (ANDROID)
66 set(CMAKE_SKIP_INSTALL_RULES ON)
67 set(LLVM_VERSION "15.0.6")
68
69 # Note: even though libcxx and libcxxabi have separate releases on the project page,
70 # the separated releases cannot be compiled. Only in-tree builds work. Therefore we
71 # must fetch the source release for the entire llvm tree.
72 FetchContent_Declare(llvm
73 URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz"
74 URL_HASH SHA256=9d53ad04dc60cb7b30e810faf64c5ab8157dadef46c8766f67f286238256ff92
75 TLS_VERIFY TRUE
76 )
77 FetchContent_MakeAvailable(llvm)
78
79 # libcxx has support for most of the range library, but it's gated behind a flag:
80 add_compile_definitions(_LIBCPP_ENABLE_EXPERIMENTAL)
81
82 # Disable standard header inclusion
83 set(ANDROID_STL "none")
84
85 # libcxxabi
86 set(LIBCXXABI_INCLUDE_TESTS OFF)
87 set(LIBCXXABI_ENABLE_SHARED FALSE)
88 set(LIBCXXABI_ENABLE_STATIC TRUE)
89 set(LIBCXXABI_LIBCXX_INCLUDES "${LIBCXX_TARGET_INCLUDE_DIRECTORY}" CACHE STRING "" FORCE)
90 add_subdirectory("${llvm_SOURCE_DIR}/libcxxabi" "${llvm_BINARY_DIR}/libcxxabi")
91 link_libraries(cxxabi_static)
92
93 # libcxx
94 set(LIBCXX_ABI_NAMESPACE "__ndk1" CACHE STRING "" FORCE)
95 set(LIBCXX_CXX_ABI "libcxxabi")
96 set(LIBCXX_INCLUDE_TESTS OFF)
97 set(LIBCXX_INCLUDE_BENCHMARKS OFF)
98 set(LIBCXX_INCLUDE_DOCS OFF)
99 set(LIBCXX_ENABLE_SHARED FALSE)
100 set(LIBCXX_ENABLE_STATIC TRUE)
101 set(LIBCXX_ENABLE_ASSERTIONS FALSE)
102 add_subdirectory("${llvm_SOURCE_DIR}/libcxx" "${llvm_BINARY_DIR}/libcxx")
103 set_target_properties(cxx-headers PROPERTIES INTERFACE_COMPILE_OPTIONS "-isystem${CMAKE_BINARY_DIR}/${LIBCXX_INSTALL_INCLUDE_DIR}")
104 link_libraries(cxx_static cxx-headers)
105endif()
106
63if (YUZU_USE_BUNDLED_VCPKG) 107if (YUZU_USE_BUNDLED_VCPKG)
108 if (ANDROID)
109 set(ENV{ANDROID_NDK_HOME} "${ANDROID_NDK}")
110 list(APPEND VCPKG_MANIFEST_FEATURES "android")
111
112 if (CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")
113 set(VCPKG_TARGET_TRIPLET "arm64-android")
114 # this is to avoid CMake using the host pkg-config to find the host
115 # libraries when building for Android targets
116 set(PKG_CONFIG_EXECUTABLE "aarch64-none-linux-android-pkg-config" CACHE FILEPATH "" FORCE)
117 elseif (CMAKE_ANDROID_ARCH_ABI STREQUAL "x86_64")
118 set(VCPKG_TARGET_TRIPLET "x64-android")
119 set(PKG_CONFIG_EXECUTABLE "x86_64-none-linux-android-pkg-config" CACHE FILEPATH "" FORCE)
120 else()
121 message(FATAL_ERROR "Unsupported Android architecture ${CMAKE_ANDROID_ARCH_ABI}")
122 endif()
123 endif()
124
64 if (YUZU_TESTS) 125 if (YUZU_TESTS)
65 list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests") 126 list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
66 endif() 127 endif()
@@ -194,7 +255,7 @@ endif()
194# boost asio's concept usage doesn't play nicely with some compilers yet. 255# boost asio's concept usage doesn't play nicely with some compilers yet.
195add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS) 256add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS)
196if (MSVC) 257if (MSVC)
197 add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>) 258 add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++20>)
198 259
199 # boost still makes use of deprecated result_of. 260 # boost still makes use of deprecated result_of.
200 add_definitions(-D_HAS_DEPRECATED_RESULT_OF) 261 add_definitions(-D_HAS_DEPRECATED_RESULT_OF)
@@ -457,7 +518,7 @@ set(FFmpeg_COMPONENTS
457 avutil 518 avutil
458 swscale) 519 swscale)
459 520
460if (UNIX AND NOT APPLE) 521if (UNIX AND NOT APPLE AND NOT ANDROID)
461 find_package(PkgConfig REQUIRED) 522 find_package(PkgConfig REQUIRED)
462 pkg_check_modules(LIBVA libva) 523 pkg_check_modules(LIBVA libva)
463endif() 524endif()
diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake
index 8fe5ba48d..972f5ca74 100644
--- a/CMakeModules/DownloadExternals.cmake
+++ b/CMakeModules/DownloadExternals.cmake
@@ -7,6 +7,7 @@
7# prefix_var: name of a variable which will be set with the path to the extracted contents 7# prefix_var: name of a variable which will be set with the path to the extracted contents
8function(download_bundled_external remote_path lib_name prefix_var) 8function(download_bundled_external remote_path lib_name prefix_var)
9 9
10set(package_base_url "https://github.com/yuzu-emu/")
10set(package_repo "no_platform") 11set(package_repo "no_platform")
11set(package_extension "no_platform") 12set(package_extension "no_platform")
12if (WIN32) 13if (WIN32)
@@ -15,10 +16,13 @@ if (WIN32)
15elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 16elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
16 set(package_repo "ext-linux-bin/raw/main/") 17 set(package_repo "ext-linux-bin/raw/main/")
17 set(package_extension ".tar.xz") 18 set(package_extension ".tar.xz")
19elseif (ANDROID)
20 set(package_repo "ext-android-bin/raw/main/")
21 set(package_extension ".tar.xz")
18else() 22else()
19 message(FATAL_ERROR "No package available for this platform") 23 message(FATAL_ERROR "No package available for this platform")
20endif() 24endif()
21set(package_url "https://github.com/yuzu-emu/${package_repo}") 25set(package_url "${package_base_url}${package_repo}")
22 26
23set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") 27set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}")
24if (NOT EXISTS "${prefix}") 28if (NOT EXISTS "${prefix}")
diff --git a/LICENSES/MPL-2.0.txt b/LICENSES/MPL-2.0.txt
new file mode 100644
index 000000000..14e2f777f
--- /dev/null
+++ b/LICENSES/MPL-2.0.txt
@@ -0,0 +1,373 @@
1Mozilla Public License Version 2.0
2==================================
3
41. Definitions
5--------------
6
71.1. "Contributor"
8 means each individual or legal entity that creates, contributes to
9 the creation of, or owns Covered Software.
10
111.2. "Contributor Version"
12 means the combination of the Contributions of others (if any) used
13 by a Contributor and that particular Contributor's Contribution.
14
151.3. "Contribution"
16 means Covered Software of a particular Contributor.
17
181.4. "Covered Software"
19 means Source Code Form to which the initial Contributor has attached
20 the notice in Exhibit A, the Executable Form of such Source Code
21 Form, and Modifications of such Source Code Form, in each case
22 including portions thereof.
23
241.5. "Incompatible With Secondary Licenses"
25 means
26
27 (a) that the initial Contributor has attached the notice described
28 in Exhibit B to the Covered Software; or
29
30 (b) that the Covered Software was made available under the terms of
31 version 1.1 or earlier of the License, but not also under the
32 terms of a Secondary License.
33
341.6. "Executable Form"
35 means any form of the work other than Source Code Form.
36
371.7. "Larger Work"
38 means a work that combines Covered Software with other material, in
39 a separate file or files, that is not Covered Software.
40
411.8. "License"
42 means this document.
43
441.9. "Licensable"
45 means having the right to grant, to the maximum extent possible,
46 whether at the time of the initial grant or subsequently, any and
47 all of the rights conveyed by this License.
48
491.10. "Modifications"
50 means any of the following:
51
52 (a) any file in Source Code Form that results from an addition to,
53 deletion from, or modification of the contents of Covered
54 Software; or
55
56 (b) any new file in Source Code Form that contains any Covered
57 Software.
58
591.11. "Patent Claims" of a Contributor
60 means any patent claim(s), including without limitation, method,
61 process, and apparatus claims, in any patent Licensable by such
62 Contributor that would be infringed, but for the grant of the
63 License, by the making, using, selling, offering for sale, having
64 made, import, or transfer of either its Contributions or its
65 Contributor Version.
66
671.12. "Secondary License"
68 means either the GNU General Public License, Version 2.0, the GNU
69 Lesser General Public License, Version 2.1, the GNU Affero General
70 Public License, Version 3.0, or any later versions of those
71 licenses.
72
731.13. "Source Code Form"
74 means the form of the work preferred for making modifications.
75
761.14. "You" (or "Your")
77 means an individual or a legal entity exercising rights under this
78 License. For legal entities, "You" includes any entity that
79 controls, is controlled by, or is under common control with You. For
80 purposes of this definition, "control" means (a) the power, direct
81 or indirect, to cause the direction or management of such entity,
82 whether by contract or otherwise, or (b) ownership of more than
83 fifty percent (50%) of the outstanding shares or beneficial
84 ownership of such entity.
85
862. License Grants and Conditions
87--------------------------------
88
892.1. Grants
90
91Each Contributor hereby grants You a world-wide, royalty-free,
92non-exclusive license:
93
94(a) under intellectual property rights (other than patent or trademark)
95 Licensable by such Contributor to use, reproduce, make available,
96 modify, display, perform, distribute, and otherwise exploit its
97 Contributions, either on an unmodified basis, with Modifications, or
98 as part of a Larger Work; and
99
100(b) under Patent Claims of such Contributor to make, use, sell, offer
101 for sale, have made, import, and otherwise transfer either its
102 Contributions or its Contributor Version.
103
1042.2. Effective Date
105
106The licenses granted in Section 2.1 with respect to any Contribution
107become effective for each Contribution on the date the Contributor first
108distributes such Contribution.
109
1102.3. Limitations on Grant Scope
111
112The licenses granted in this Section 2 are the only rights granted under
113this License. No additional rights or licenses will be implied from the
114distribution or licensing of Covered Software under this License.
115Notwithstanding Section 2.1(b) above, no patent license is granted by a
116Contributor:
117
118(a) for any code that a Contributor has removed from Covered Software;
119 or
120
121(b) for infringements caused by: (i) Your and any other third party's
122 modifications of Covered Software, or (ii) the combination of its
123 Contributions with other software (except as part of its Contributor
124 Version); or
125
126(c) under Patent Claims infringed by Covered Software in the absence of
127 its Contributions.
128
129This License does not grant any rights in the trademarks, service marks,
130or logos of any Contributor (except as may be necessary to comply with
131the notice requirements in Section 3.4).
132
1332.4. Subsequent Licenses
134
135No Contributor makes additional grants as a result of Your choice to
136distribute the Covered Software under a subsequent version of this
137License (see Section 10.2) or under the terms of a Secondary License (if
138permitted under the terms of Section 3.3).
139
1402.5. Representation
141
142Each Contributor represents that the Contributor believes its
143Contributions are its original creation(s) or it has sufficient rights
144to grant the rights to its Contributions conveyed by this License.
145
1462.6. Fair Use
147
148This License is not intended to limit any rights You have under
149applicable copyright doctrines of fair use, fair dealing, or other
150equivalents.
151
1522.7. Conditions
153
154Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155in Section 2.1.
156
1573. Responsibilities
158-------------------
159
1603.1. Distribution of Source Form
161
162All distribution of Covered Software in Source Code Form, including any
163Modifications that You create or to which You contribute, must be under
164the terms of this License. You must inform recipients that the Source
165Code Form of the Covered Software is governed by the terms of this
166License, and how they can obtain a copy of this License. You may not
167attempt to alter or restrict the recipients' rights in the Source Code
168Form.
169
1703.2. Distribution of Executable Form
171
172If You distribute Covered Software in Executable Form then:
173
174(a) such Covered Software must also be made available in Source Code
175 Form, as described in Section 3.1, and You must inform recipients of
176 the Executable Form how they can obtain a copy of such Source Code
177 Form by reasonable means in a timely manner, at a charge no more
178 than the cost of distribution to the recipient; and
179
180(b) You may distribute such Executable Form under the terms of this
181 License, or sublicense it under different terms, provided that the
182 license for the Executable Form does not attempt to limit or alter
183 the recipients' rights in the Source Code Form under this License.
184
1853.3. Distribution of a Larger Work
186
187You may create and distribute a Larger Work under terms of Your choice,
188provided that You also comply with the requirements of this License for
189the Covered Software. If the Larger Work is a combination of Covered
190Software with a work governed by one or more Secondary Licenses, and the
191Covered Software is not Incompatible With Secondary Licenses, this
192License permits You to additionally distribute such Covered Software
193under the terms of such Secondary License(s), so that the recipient of
194the Larger Work may, at their option, further distribute the Covered
195Software under the terms of either this License or such Secondary
196License(s).
197
1983.4. Notices
199
200You may not remove or alter the substance of any license notices
201(including copyright notices, patent notices, disclaimers of warranty,
202or limitations of liability) contained within the Source Code Form of
203the Covered Software, except that You may alter any license notices to
204the extent required to remedy known factual inaccuracies.
205
2063.5. Application of Additional Terms
207
208You may choose to offer, and to charge a fee for, warranty, support,
209indemnity or liability obligations to one or more recipients of Covered
210Software. However, You may do so only on Your own behalf, and not on
211behalf of any Contributor. You must make it absolutely clear that any
212such warranty, support, indemnity, or liability obligation is offered by
213You alone, and You hereby agree to indemnify every Contributor for any
214liability incurred by such Contributor as a result of warranty, support,
215indemnity or liability terms You offer. You may include additional
216disclaimers of warranty and limitations of liability specific to any
217jurisdiction.
218
2194. Inability to Comply Due to Statute or Regulation
220---------------------------------------------------
221
222If it is impossible for You to comply with any of the terms of this
223License with respect to some or all of the Covered Software due to
224statute, judicial order, or regulation then You must: (a) comply with
225the terms of this License to the maximum extent possible; and (b)
226describe the limitations and the code they affect. Such description must
227be placed in a text file included with all distributions of the Covered
228Software under this License. Except to the extent prohibited by statute
229or regulation, such description must be sufficiently detailed for a
230recipient of ordinary skill to be able to understand it.
231
2325. Termination
233--------------
234
2355.1. The rights granted under this License will terminate automatically
236if You fail to comply with any of its terms. However, if You become
237compliant, then the rights granted under this License from a particular
238Contributor are reinstated (a) provisionally, unless and until such
239Contributor explicitly and finally terminates Your grants, and (b) on an
240ongoing basis, if such Contributor fails to notify You of the
241non-compliance by some reasonable means prior to 60 days after You have
242come back into compliance. Moreover, Your grants from a particular
243Contributor are reinstated on an ongoing basis if such Contributor
244notifies You of the non-compliance by some reasonable means, this is the
245first time You have received notice of non-compliance with this License
246from such Contributor, and You become compliant prior to 30 days after
247Your receipt of the notice.
248
2495.2. If You initiate litigation against any entity by asserting a patent
250infringement claim (excluding declaratory judgment actions,
251counter-claims, and cross-claims) alleging that a Contributor Version
252directly or indirectly infringes any patent, then the rights granted to
253You by any and all Contributors for the Covered Software under Section
2542.1 of this License shall terminate.
255
2565.3. In the event of termination under Sections 5.1 or 5.2 above, all
257end user license agreements (excluding distributors and resellers) which
258have been validly granted by You or Your distributors under this License
259prior to termination shall survive termination.
260
261************************************************************************
262* *
263* 6. Disclaimer of Warranty *
264* ------------------------- *
265* *
266* Covered Software is provided under this License on an "as is" *
267* basis, without warranty of any kind, either expressed, implied, or *
268* statutory, including, without limitation, warranties that the *
269* Covered Software is free of defects, merchantable, fit for a *
270* particular purpose or non-infringing. The entire risk as to the *
271* quality and performance of the Covered Software is with You. *
272* Should any Covered Software prove defective in any respect, You *
273* (not any Contributor) assume the cost of any necessary servicing, *
274* repair, or correction. This disclaimer of warranty constitutes an *
275* essential part of this License. No use of any Covered Software is *
276* authorized under this License except under this disclaimer. *
277* *
278************************************************************************
279
280************************************************************************
281* *
282* 7. Limitation of Liability *
283* -------------------------- *
284* *
285* Under no circumstances and under no legal theory, whether tort *
286* (including negligence), contract, or otherwise, shall any *
287* Contributor, or anyone who distributes Covered Software as *
288* permitted above, be liable to You for any direct, indirect, *
289* special, incidental, or consequential damages of any character *
290* including, without limitation, damages for lost profits, loss of *
291* goodwill, work stoppage, computer failure or malfunction, or any *
292* and all other commercial damages or losses, even if such party *
293* shall have been informed of the possibility of such damages. This *
294* limitation of liability shall not apply to liability for death or *
295* personal injury resulting from such party's negligence to the *
296* extent applicable law prohibits such limitation. Some *
297* jurisdictions do not allow the exclusion or limitation of *
298* incidental or consequential damages, so this exclusion and *
299* limitation may not apply to You. *
300* *
301************************************************************************
302
3038. Litigation
304-------------
305
306Any litigation relating to this License may be brought only in the
307courts of a jurisdiction where the defendant maintains its principal
308place of business and such litigation shall be governed by laws of that
309jurisdiction, without reference to its conflict-of-law provisions.
310Nothing in this Section shall prevent a party's ability to bring
311cross-claims or counter-claims.
312
3139. Miscellaneous
314----------------
315
316This License represents the complete agreement concerning the subject
317matter hereof. If any provision of this License is held to be
318unenforceable, such provision shall be reformed only to the extent
319necessary to make it enforceable. Any law or regulation which provides
320that the language of a contract shall be construed against the drafter
321shall not be used to construe this License against a Contributor.
322
32310. Versions of the License
324---------------------------
325
32610.1. New Versions
327
328Mozilla Foundation is the license steward. Except as provided in Section
32910.3, no one other than the license steward has the right to modify or
330publish new versions of this License. Each version will be given a
331distinguishing version number.
332
33310.2. Effect of New Versions
334
335You may distribute the Covered Software under the terms of the version
336of the License under which You originally received the Covered Software,
337or under the terms of any subsequent version published by the license
338steward.
339
34010.3. Modified Versions
341
342If you create software not governed by this License, and you want to
343create a new license for such software, you may create and use a
344modified version of this License if you rename the license and remove
345any references to the name of the license steward (except to note that
346such modified license differs from this License).
347
34810.4. Distributing Source Code Form that is Incompatible With Secondary
349Licenses
350
351If You choose to distribute Source Code Form that is Incompatible With
352Secondary Licenses under the terms of this version of the License, the
353notice described in Exhibit B of this License must be attached.
354
355Exhibit A - Source Code Form License Notice
356-------------------------------------------
357
358 This Source Code Form is subject to the terms of the Mozilla Public
359 License, v. 2.0. If a copy of the MPL was not distributed with this
360 file, You can obtain one at http://mozilla.org/MPL/2.0/.
361
362If it is not possible or desirable to put the notice in a particular
363file, then You may include the notice in a location (such as a LICENSE
364file in a relevant directory) where a recipient would be likely to look
365for such a notice.
366
367You may add additional accurate notices of copyright ownership.
368
369Exhibit B - "Incompatible With Secondary Licenses" Notice
370---------------------------------------------------------
371
372 This Source Code Form is "Incompatible With Secondary Licenses", as
373 defined by the Mozilla Public License, v. 2.0.
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index a934f0c86..e48137080 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -150,3 +150,9 @@ endif()
150 150
151add_library(stb stb/stb_dxt.cpp) 151add_library(stb stb/stb_dxt.cpp)
152target_include_directories(stb PUBLIC ./stb) 152target_include_directories(stb PUBLIC ./stb)
153
154if (ANDROID)
155 if (ARCHITECTURE_arm64)
156 add_subdirectory(libadrenotools)
157 endif()
158endif()
diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt
index 03fad0778..0a926e399 100644
--- a/externals/ffmpeg/CMakeLists.txt
+++ b/externals/ffmpeg/CMakeLists.txt
@@ -1,7 +1,7 @@
1# SPDX-FileCopyrightText: 2021 yuzu Emulator Project 1# SPDX-FileCopyrightText: 2021 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later 2# SPDX-License-Identifier: GPL-2.0-or-later
3 3
4if (NOT WIN32) 4if (NOT WIN32 AND NOT ANDROID)
5 # Build FFmpeg from externals 5 # Build FFmpeg from externals
6 message(STATUS "Using FFmpeg from externals") 6 message(STATUS "Using FFmpeg from externals")
7 7
@@ -44,10 +44,12 @@ if (NOT WIN32)
44 endforeach() 44 endforeach()
45 45
46 find_package(PkgConfig REQUIRED) 46 find_package(PkgConfig REQUIRED)
47 pkg_check_modules(LIBVA libva) 47 if (NOT ANDROID)
48 pkg_check_modules(CUDA cuda) 48 pkg_check_modules(LIBVA libva)
49 pkg_check_modules(FFNVCODEC ffnvcodec) 49 pkg_check_modules(CUDA cuda)
50 pkg_check_modules(VDPAU vdpau) 50 pkg_check_modules(FFNVCODEC ffnvcodec)
51 pkg_check_modules(VDPAU vdpau)
52 endif()
51 53
52 set(FFmpeg_HWACCEL_LIBRARIES) 54 set(FFmpeg_HWACCEL_LIBRARIES)
53 set(FFmpeg_HWACCEL_FLAGS) 55 set(FFmpeg_HWACCEL_FLAGS)
@@ -121,6 +123,26 @@ if (NOT WIN32)
121 list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vdpau) 123 list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vdpau)
122 endif() 124 endif()
123 125
126 find_program(BASH_PROGRAM bash REQUIRED)
127
128 set(FFmpeg_CROSS_COMPILE_FLAGS "")
129 if (ANDROID)
130 string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" FFmpeg_HOST_SYSTEM_NAME)
131 set(TOOLCHAIN "${ANDROID_NDK}/toolchains/llvm/prebuilt/${FFmpeg_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}")
132 set(SYSROOT "${TOOLCHAIN}/sysroot")
133 set(FFmpeg_CPU "armv8-a")
134 list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
135 --arch=arm64
136 #--cpu=${FFmpeg_CPU}
137 --enable-cross-compile
138 --cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android-
139 --sysroot=${SYSROOT}
140 --target-os=android
141 --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld"
142 --extra-ldflags="-nostdlib"
143 )
144 endif()
145
124 # `configure` parameters builds only exactly what yuzu needs from FFmpeg 146 # `configure` parameters builds only exactly what yuzu needs from FFmpeg
125 # `--disable-vdpau` is needed to avoid linking issues 147 # `--disable-vdpau` is needed to avoid linking issues
126 set(FFmpeg_CC ${CMAKE_C_COMPILER_LAUNCHER} ${CMAKE_C_COMPILER}) 148 set(FFmpeg_CC ${CMAKE_C_COMPILER_LAUNCHER} ${CMAKE_C_COMPILER})
@@ -129,7 +151,7 @@ if (NOT WIN32)
129 OUTPUT 151 OUTPUT
130 ${FFmpeg_MAKEFILE} 152 ${FFmpeg_MAKEFILE}
131 COMMAND 153 COMMAND
132 /bin/bash ${FFmpeg_PREFIX}/configure 154 ${BASH_PROGRAM} ${FFmpeg_PREFIX}/configure
133 --disable-avdevice 155 --disable-avdevice
134 --disable-avformat 156 --disable-avformat
135 --disable-doc 157 --disable-doc
@@ -146,12 +168,14 @@ if (NOT WIN32)
146 --cc="${FFmpeg_CC}" 168 --cc="${FFmpeg_CC}"
147 --cxx="${FFmpeg_CXX}" 169 --cxx="${FFmpeg_CXX}"
148 ${FFmpeg_HWACCEL_FLAGS} 170 ${FFmpeg_HWACCEL_FLAGS}
171 ${FFmpeg_CROSS_COMPILE_FLAGS}
149 WORKING_DIRECTORY 172 WORKING_DIRECTORY
150 ${FFmpeg_BUILD_DIR} 173 ${FFmpeg_BUILD_DIR}
151 ) 174 )
152 unset(FFmpeg_CC) 175 unset(FFmpeg_CC)
153 unset(FFmpeg_CXX) 176 unset(FFmpeg_CXX)
154 unset(FFmpeg_HWACCEL_FLAGS) 177 unset(FFmpeg_HWACCEL_FLAGS)
178 unset(FFmpeg_CROSS_COMPILE_FLAGS)
155 179
156 # Workaround for Ubuntu 18.04's older version of make not being able to call make as a child 180 # Workaround for Ubuntu 18.04's older version of make not being able to call make as a child
157 # with context of the jobserver. Also helps ninja users. 181 # with context of the jobserver. Also helps ninja users.
@@ -197,7 +221,38 @@ if (NOT WIN32)
197 else() 221 else()
198 message(FATAL_ERROR "FFmpeg not found") 222 message(FATAL_ERROR "FFmpeg not found")
199 endif() 223 endif()
200else(WIN32) 224elseif(ANDROID)
225 # Use yuzu FFmpeg binaries
226 if (ARCHITECTURE_arm64)
227 set(FFmpeg_EXT_NAME "ffmpeg-android-v5.1.LTS-aarch64")
228 elseif (ARCHITECTURE_x86_64)
229 set(FFmpeg_EXT_NAME "ffmpeg-android-v5.1.LTS-x86_64")
230 else()
231 message(FATAL_ERROR "Unsupported architecture for Android FFmpeg")
232 endif()
233 set(FFmpeg_PATH "${CMAKE_BINARY_DIR}/externals/${FFmpeg_EXT_NAME}")
234 download_bundled_external("ffmpeg/" ${FFmpeg_EXT_NAME} "")
235 set(FFmpeg_FOUND YES)
236 set(FFmpeg_INCLUDE_DIR "${FFmpeg_PATH}/include" CACHE PATH "Path to FFmpeg headers" FORCE)
237 set(FFmpeg_LIBRARY_DIR "${FFmpeg_PATH}/lib" CACHE PATH "Path to FFmpeg library directory" FORCE)
238 set(FFmpeg_LDFLAGS "" CACHE STRING "FFmpeg linker flags" FORCE)
239 set(FFmpeg_LIBRARIES
240 ${FFmpeg_LIBRARY_DIR}/libavcodec.so
241 ${FFmpeg_LIBRARY_DIR}/libavdevice.so
242 ${FFmpeg_LIBRARY_DIR}/libavfilter.so
243 ${FFmpeg_LIBRARY_DIR}/libavformat.so
244 ${FFmpeg_LIBRARY_DIR}/libavutil.so
245 ${FFmpeg_LIBRARY_DIR}/libswresample.so
246 ${FFmpeg_LIBRARY_DIR}/libswscale.so
247 ${FFmpeg_LIBRARY_DIR}/libvpx.a
248 ${FFmpeg_LIBRARY_DIR}/libx264.a
249 CACHE PATH "Paths to FFmpeg libraries" FORCE)
250 # exported variables
251 set(FFmpeg_PATH "${FFmpeg_PATH}" PARENT_SCOPE)
252 set(FFmpeg_LDFLAGS "${FFmpeg_LDFLAGS}" PARENT_SCOPE)
253 set(FFmpeg_LIBRARIES "${FFmpeg_LIBRARIES}" PARENT_SCOPE)
254 set(FFmpeg_INCLUDE_DIR "${FFmpeg_INCLUDE_DIR}" PARENT_SCOPE)
255elseif(WIN32)
201 # Use yuzu FFmpeg binaries 256 # Use yuzu FFmpeg binaries
202 set(FFmpeg_EXT_NAME "ffmpeg-5.1.3") 257 set(FFmpeg_EXT_NAME "ffmpeg-5.1.3")
203 set(FFmpeg_PATH "${CMAKE_BINARY_DIR}/externals/${FFmpeg_EXT_NAME}") 258 set(FFmpeg_PATH "${CMAKE_BINARY_DIR}/externals/${FFmpeg_EXT_NAME}")
@@ -206,7 +261,6 @@ else(WIN32)
206 set(FFmpeg_INCLUDE_DIR "${FFmpeg_PATH}/include" CACHE PATH "Path to FFmpeg headers" FORCE) 261 set(FFmpeg_INCLUDE_DIR "${FFmpeg_PATH}/include" CACHE PATH "Path to FFmpeg headers" FORCE)
207 set(FFmpeg_LIBRARY_DIR "${FFmpeg_PATH}/bin" CACHE PATH "Path to FFmpeg library directory" FORCE) 262 set(FFmpeg_LIBRARY_DIR "${FFmpeg_PATH}/bin" CACHE PATH "Path to FFmpeg library directory" FORCE)
208 set(FFmpeg_LDFLAGS "" CACHE STRING "FFmpeg linker flags" FORCE) 263 set(FFmpeg_LDFLAGS "" CACHE STRING "FFmpeg linker flags" FORCE)
209 set(FFmpeg_DLL_DIR "${FFmpeg_PATH}/bin" CACHE PATH "Path to FFmpeg dll's" FORCE)
210 set(FFmpeg_LIBRARIES 264 set(FFmpeg_LIBRARIES
211 ${FFmpeg_LIBRARY_DIR}/swscale.lib 265 ${FFmpeg_LIBRARY_DIR}/swscale.lib
212 ${FFmpeg_LIBRARY_DIR}/avcodec.lib 266 ${FFmpeg_LIBRARY_DIR}/avcodec.lib
diff --git a/externals/libadrenotools b/externals/libadrenotools
new file mode 160000
Subproject 5cd3f5c5ceea6d9e9d435ccdd922d9b99e55d10
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5e3a74c0f..0696201df 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -43,7 +43,7 @@ if (MSVC)
43 /Zo 43 /Zo
44 /permissive- 44 /permissive-
45 /EHsc 45 /EHsc
46 /std:c++latest 46 /std:c++20
47 /utf-8 47 /utf-8
48 /volatile:iso 48 /volatile:iso
49 /Zc:externConstexpr 49 /Zc:externConstexpr
@@ -51,8 +51,10 @@ if (MSVC)
51 /Zc:throwingNew 51 /Zc:throwingNew
52 /GT 52 /GT
53 53
54 # Modules
55 /experimental:module- # Disable module support explicitly due to conflicts with precompiled headers
56
54 # External headers diagnostics 57 # External headers diagnostics
55 /experimental:external # Enables the external headers options. This option isn't required in Visual Studio 2019 version 16.10 and later
56 /external:anglebrackets # Treats all headers included by #include <header>, where the header file is enclosed in angle brackets (< >), as external headers 58 /external:anglebrackets # Treats all headers included by #include <header>, where the header file is enclosed in angle brackets (< >), as external headers
57 /external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers 59 /external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers
58 60
@@ -195,3 +197,8 @@ endif()
195if (ENABLE_WEB_SERVICE) 197if (ENABLE_WEB_SERVICE)
196 add_subdirectory(web_service) 198 add_subdirectory(web_service)
197endif() 199endif()
200
201if (ANDROID)
202 add_subdirectory(android/app/src/main/jni)
203 target_include_directories(yuzu-android PRIVATE android/app/src/main)
204endif()
diff --git a/src/android/.gitignore b/src/android/.gitignore
new file mode 100644
index 000000000..121cc8484
--- /dev/null
+++ b/src/android/.gitignore
@@ -0,0 +1,65 @@
1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4# Built application files
5*.apk
6*.ap_
7
8# Files for the ART/Dalvik VM
9*.dex
10
11# Java class files
12*.class
13
14# Generated files
15bin/
16gen/
17out/
18
19# Gradle files
20.gradle/
21build/
22
23# Local configuration file (sdk path, etc)
24local.properties
25
26# Proguard folder generated by Eclipse
27proguard/
28
29# Log Files
30*.log
31
32# Android Studio Navigation editor temp files
33.navigation/
34
35# Android Studio captures folder
36captures/
37
38# IntelliJ
39*.iml
40.idea/
41
42# Keystore files
43# Uncomment the following line if you do not want to check your keystore files in.
44#*.jks
45
46# External native build folder generated in Android Studio 2.2 and later
47.externalNativeBuild
48
49# CXX compile cache
50app/.cxx
51
52# Google Services (e.g. APIs or Firebase)
53google-services.json
54
55# Freeline
56freeline.py
57freeline/
58freeline_project_description.json
59
60# fastlane
61fastlane/report.xml
62fastlane/Preview.html
63fastlane/screenshots
64fastlane/test_output
65fastlane/readme.md
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
new file mode 100644
index 000000000..fe613d339
--- /dev/null
+++ b/src/android/app/build.gradle.kts
@@ -0,0 +1,258 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4import android.annotation.SuppressLint
5import org.jetbrains.kotlin.konan.properties.Properties
6
7plugins {
8 id("com.android.application")
9 id("org.jetbrains.kotlin.android")
10 id("kotlin-parcelize")
11 kotlin("plugin.serialization") version "1.8.21"
12}
13
14/**
15 * Use the number of seconds/10 since Jan 1 2016 as the versionCode.
16 * This lets us upload a new build at most every 10 seconds for the
17 * next 680 years.
18 */
19val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toInt()
20
21@Suppress("UnstableApiUsage")
22android {
23 namespace = "org.yuzu.yuzu_emu"
24
25 compileSdkVersion = "android-33"
26 ndkVersion = "25.2.9519653"
27
28 buildFeatures {
29 viewBinding = true
30 }
31
32 compileOptions {
33 sourceCompatibility = JavaVersion.VERSION_17
34 targetCompatibility = JavaVersion.VERSION_17
35 }
36
37 kotlinOptions {
38 jvmTarget = "17"
39 }
40
41 packaging {
42 // This is necessary for libadrenotools custom driver loading
43 jniLibs.useLegacyPackaging = true
44 }
45
46 lint {
47 // This is important as it will run lint but not abort on error
48 // Lint has some overly obnoxious "errors" that should really be warnings
49 abortOnError = false
50
51 //Uncomment disable lines for test builds...
52 //disable 'MissingTranslation'bin
53 //disable 'ExtraTranslation'
54 }
55
56 defaultConfig {
57 // TODO If this is ever modified, change application_id in strings.xml
58 applicationId = "org.yuzu.yuzu_emu"
59 minSdk = 30
60 targetSdk = 33
61 versionName = getGitVersion()
62
63 // If you want to use autoVersion for the versionCode, create a property in local.properties
64 // named "autoVersioned" and set it to "true"
65 val properties = Properties()
66 val versionProperty = try {
67 properties.load(project.rootProject.file("local.properties").inputStream())
68 properties.getProperty("autoVersioned") ?: ""
69 } catch (e: Exception) { "" }
70
71 versionCode = if (versionProperty == "true") {
72 autoVersion
73 } else {
74 1
75 }
76
77 ndk {
78 @SuppressLint("ChromeOsAbiSupport")
79 abiFilters += listOf("arm64-v8a")
80 }
81
82 buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
83 buildConfigField("String", "BRANCH", "\"${getBranch()}\"")
84 }
85
86 // Define build types, which are orthogonal to product flavors.
87 buildTypes {
88
89 // Signed by release key, allowing for upload to Play Store.
90 release {
91 resValue("string", "app_name_suffixed", "yuzu")
92 signingConfig = signingConfigs.getByName("debug")
93 isMinifyEnabled = true
94 isDebuggable = false
95 proguardFiles(
96 getDefaultProguardFile("proguard-android.txt"),
97 "proguard-rules.pro"
98 )
99 }
100
101 // builds a release build that doesn't need signing
102 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
103 register("relWithDebInfo") {
104 resValue("string", "app_name_suffixed", "yuzu Debug Release")
105 signingConfig = signingConfigs.getByName("debug")
106 isMinifyEnabled = true
107 isDebuggable = true
108 proguardFiles(
109 getDefaultProguardFile("proguard-android.txt"),
110 "proguard-rules.pro"
111 )
112 versionNameSuffix = "-relWithDebInfo"
113 applicationIdSuffix = ".relWithDebInfo"
114 isJniDebuggable = true
115 }
116
117 // Signed by debug key disallowing distribution on Play Store.
118 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
119 debug {
120 resValue("string", "app_name_suffixed", "yuzu Debug")
121 isDebuggable = true
122 isJniDebuggable = true
123 versionNameSuffix = "-debug"
124 applicationIdSuffix = ".debug"
125 }
126 }
127
128 flavorDimensions.add("version")
129 productFlavors {
130 create("mainline") {
131 dimension = "version"
132 buildConfigField("Boolean", "PREMIUM", "false")
133 }
134
135 create("ea") {
136 dimension = "version"
137 buildConfigField("Boolean", "PREMIUM", "true")
138 applicationIdSuffix = ".ea"
139 }
140 }
141
142 externalNativeBuild {
143 cmake {
144 version = "3.22.1"
145 path = file("../../../CMakeLists.txt")
146 }
147 }
148
149 defaultConfig {
150 externalNativeBuild {
151 cmake {
152 arguments(
153 "-DENABLE_QT=0", // Don't use QT
154 "-DENABLE_SDL2=0", // Don't use SDL
155 "-DENABLE_WEB_SERVICE=0", // Don't use telemetry
156 "-DBUNDLE_SPEEX=ON",
157 "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
158 "-DYUZU_USE_BUNDLED_VCPKG=ON",
159 "-DYUZU_USE_BUNDLED_FFMPEG=ON",
160 "-DYUZU_ENABLE_LTO=ON"
161 )
162
163 abiFilters("arm64-v8a", "x86_64")
164 }
165 }
166 }
167}
168
169dependencies {
170 implementation("androidx.core:core-ktx:1.10.1")
171 implementation("androidx.appcompat:appcompat:1.6.1")
172 implementation("androidx.recyclerview:recyclerview:1.3.0")
173 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
174 implementation("androidx.fragment:fragment-ktx:1.6.0")
175 implementation("androidx.documentfile:documentfile:1.0.1")
176 implementation("com.google.android.material:material:1.9.0")
177 implementation("androidx.preference:preference:1.2.0")
178 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
179 implementation("io.coil-kt:coil:2.2.2")
180 implementation("androidx.core:core-splashscreen:1.0.1")
181 implementation("androidx.window:window:1.1.0")
182 implementation("org.ini4j:ini4j:0.5.4")
183 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
184 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
185 implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
186 implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
187 implementation("info.debatty:java-string-similarity:2.0.0")
188 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
189}
190
191fun getGitVersion(): String {
192 var versionName = "0.0"
193
194 try {
195 versionName = ProcessBuilder("git", "describe", "--always", "--long")
196 .directory(project.rootDir)
197 .redirectOutput(ProcessBuilder.Redirect.PIPE)
198 .redirectError(ProcessBuilder.Redirect.PIPE)
199 .start().inputStream.bufferedReader().use { it.readText() }
200 .trim()
201 .replace(Regex("(-0)?-[^-]+$"), "")
202 } catch (e: Exception) {
203 logger.error("Cannot find git, defaulting to dummy version number")
204 }
205
206 if (System.getenv("GITHUB_ACTIONS") != null) {
207 val gitTag = System.getenv("GIT_TAG_NAME")
208 versionName = gitTag ?: versionName
209 }
210
211 return versionName
212}
213
214fun getGitHash(): String {
215 try {
216 val processBuilder = ProcessBuilder("git", "rev-parse", "--short", "HEAD")
217 processBuilder.directory(project.rootDir)
218 val process = processBuilder.start()
219 val inputStream = process.inputStream
220 val errorStream = process.errorStream
221 process.waitFor()
222
223 return if (process.exitValue() == 0) {
224 inputStream.bufferedReader()
225 .use { it.readText().trim() } // return the value of gitHash
226 } else {
227 val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
228 logger.error("Error running git command: $errorMessage")
229 "dummy-hash" // return a dummy hash value in case of an error
230 }
231 } catch (e: Exception) {
232 logger.error("$e: Cannot find git, defaulting to dummy build hash")
233 return "dummy-hash" // return a dummy hash value in case of an error
234 }
235}
236
237fun getBranch(): String {
238 try {
239 val processBuilder = ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")
240 processBuilder.directory(project.rootDir)
241 val process = processBuilder.start()
242 val inputStream = process.inputStream
243 val errorStream = process.errorStream
244 process.waitFor()
245
246 return if (process.exitValue() == 0) {
247 inputStream.bufferedReader()
248 .use { it.readText().trim() } // return the value of gitHash
249 } else {
250 val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
251 logger.error("Error running git command: $errorMessage")
252 "dummy-hash" // return a dummy hash value in case of an error
253 }
254 } catch (e: Exception) {
255 logger.error("$e: Cannot find git, defaulting to dummy build hash")
256 return "dummy-hash" // return a dummy hash value in case of an error
257 }
258}
diff --git a/src/android/app/proguard-rules.pro b/src/android/app/proguard-rules.pro
new file mode 100644
index 000000000..691e08fd0
--- /dev/null
+++ b/src/android/app/proguard-rules.pro
@@ -0,0 +1,24 @@
1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4# To get usable stack traces
5-dontobfuscate
6
7# Prevents crashing when using Wini
8-keep class org.ini4j.spi.IniParser
9-keep class org.ini4j.spi.IniBuilder
10-keep class org.ini4j.spi.IniFormatter
11
12# Suppress warnings for R8
13-dontwarn org.bouncycastle.jsse.BCSSLParameters
14-dontwarn org.bouncycastle.jsse.BCSSLSocket
15-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
16-dontwarn org.conscrypt.Conscrypt$Version
17-dontwarn org.conscrypt.Conscrypt
18-dontwarn org.conscrypt.ConscryptHostnameVerifier
19-dontwarn org.openjsse.javax.net.ssl.SSLParameters
20-dontwarn org.openjsse.javax.net.ssl.SSLSocket
21-dontwarn org.openjsse.net.ssl.OpenJSSE
22-dontwarn java.beans.Introspector
23-dontwarn java.beans.VetoableChangeListener
24-dontwarn java.beans.VetoableChangeSupport
diff --git a/src/android/app/src/ea/res/drawable/ic_yuzu.xml b/src/android/app/src/ea/res/drawable/ic_yuzu.xml
new file mode 100644
index 000000000..deb8ba53f
--- /dev/null
+++ b/src/android/app/src/ea/res/drawable/ic_yuzu.xml
@@ -0,0 +1,22 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="200dp"
3 android:height="200dp"
4 android:viewportWidth="500"
5 android:viewportHeight="500">
6 <path
7 android:fillColor="#C6C6C6"
8 android:fillType="nonZero"
9 android:pathData="M262.66,175.11L262.66,375.05C318.54,375.05 363.85,330.29 363.85,275.08C363.85,219.87 318.54,175.11 262.66,175.11M282.43,197.01C318.67,206 344.09,238.19 344.09,275.11C344.09,312.03 318.67,344.22 282.43,353.2L282.43,197.01"
10 android:strokeWidth="1.46"
11 android:strokeColor="#00000000"
12 android:strokeLineCap="butt"
13 android:strokeLineJoin="miter" />
14 <path
15 android:fillColor="#FFDC00"
16 android:fillType="nonZero"
17 android:pathData="M237.31,125.11C181.43,125.11 136.12,169.87 136.12,225.08C136.12,280.29 181.43,325.05 237.31,325.05ZM217.57,147.01L217.57,303.2C189.11,296.16 166.67,274.54 158.84,246.6C151.01,218.65 159,188.71 179.75,168.21C190.16,157.86 203.24,150.53 217.57,147.01"
18 android:strokeWidth="1.46"
19 android:strokeColor="#00000000"
20 android:strokeLineCap="butt"
21 android:strokeLineJoin="miter" />
22</vector>
diff --git a/src/android/app/src/ea/res/drawable/ic_yuzu_full.xml b/src/android/app/src/ea/res/drawable/ic_yuzu_full.xml
new file mode 100644
index 000000000..4ef472876
--- /dev/null
+++ b/src/android/app/src/ea/res/drawable/ic_yuzu_full.xml
@@ -0,0 +1,12 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="155.3dp"
3 android:height="172.55dp"
4 android:viewportWidth="155.3"
5 android:viewportHeight="172.55">
6 <path
7 android:fillColor="#C6C6C6"
8 android:pathData="M86.28,34.51v138a69,69 0,0 0,0 -138M99.76,49.63a55.57,55.57 0,0 1,0 107.8V49.63" />
9 <path
10 android:fillColor="#FFDC00"
11 android:pathData="M69,0a69,69 0,0 0,0 138ZM55.54,15.12v107.8A55.55,55.55 0,0 1,29.75 29.75,55.1 55.1,0 0,1 55.54,15.12" />
12</vector>
diff --git a/src/android/app/src/ea/res/drawable/ic_yuzu_title.xml b/src/android/app/src/ea/res/drawable/ic_yuzu_title.xml
new file mode 100644
index 000000000..29d0cfced
--- /dev/null
+++ b/src/android/app/src/ea/res/drawable/ic_yuzu_title.xml
@@ -0,0 +1,24 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="340.97dp"
3 android:height="389.85dp"
4 android:viewportWidth="340.97"
5 android:viewportHeight="389.85">
6 <path
7 android:fillColor="?attr/colorOnSurface"
8 android:pathData="M341,268.68v73c0,14.5 -2.24,25.24 -6.83,32.82 -5.92,10.15 -16.21,15.32 -30.54,15.32S279,384.61 273,374.27c-4.56,-7.64 -6.8,-18.42 -6.8,-32.92V268.68a4.52,4.52 0,0 1,4.51 -4.51H273a4.5,4.5 0,0 1,4.5 4.51v72.5c0,33.53 14.88,37.4 26.07,37.4 12.14,0 26.08,-4.17 26.08,-36.71V268.68a4.52,4.52 0,0 1,4.52 -4.51h2.27A4.5,4.5 0,0 1,341 268.68Z" />
9 <path
10 android:fillColor="?attr/colorOnSurface"
11 android:pathData="M246.49,389.85H178.6c-2.35,0 -4.72,-1.88 -4.72,-6.08a8.28,8.28 0,0 1,1.33 -4.48l60.33,-104.47H186a4.51,4.51 0,0 1,-4.51 -4.51v-1.58a4.51,4.51 0,0 1,4.48 -4.51h0.8c58.69,-0.11 59.12,0 59.67,0.07a5.19,5.19 0,0 1,4 5.8,8.69 8.69,0 0,1 -1.33,3.76l-60.6,104.77h58a4.51,4.51 0,0 1,4.51 4.51v2.21A4.51,4.51 0,0 1,246.49 389.85Z" />
12 <path
13 android:fillColor="?attr/colorOnSurface"
14 android:pathData="M73.6,268.68v82.06c0,26 -11.8,38.44 -37.12,39.09h-0.12a4.51,4.51 0,0 1,-4.51 -4.51V383a4.51,4.51 0,0 1,4.48 -4.5c18.49,-0.15 26,-8.23 26,-27.9v-2.37A32.34,32.34 0,0 1,59 351.46c-6.39,5.5 -14.5,8.29 -24.07,8.29C12.09,359.75 0,347.34 0,323.86V268.68a4.52,4.52 0,0 1,4.51 -4.51H6.73a4.52,4.52 0,0 1,4.5 4.51v55c0,7.6 1.82,14.22 5,18.18 3.57,4.56 9.17,6.49 18.75,6.49 10.13,0 17.32,-3.76 22,-11.5 3.61,-5.92 5.43,-13.66 5.43,-23V268.68a4.52,4.52 0,0 1,4.51 -4.51h2.22A4.52,4.52 0,0 1,73.6 268.68Z" />
15 <path
16 android:fillColor="?attr/colorOnSurface"
17 android:pathData="M163.27,268.68v73c0,14.5 -2.24,25.24 -6.84,32.82 -5.92,10.15 -16.2,15.32 -30.53,15.32s-24.62,-5.23 -30.58,-15.57c-4.56,-7.64 -6.79,-18.42 -6.79,-32.92V268.68A4.51,4.51 0,0 1,93 264.17h2.28a4.51,4.51 0,0 1,4.51 4.51v72.5c0,33.53 14.88,37.4 26.07,37.4 12.14,0 26.08,-4.17 26.08,-36.71V268.68a4.51,4.51 0,0 1,4.51 -4.51h2.27A4.51,4.51 0,0 1,163.27 268.68Z" />
18 <path
19 android:fillColor="#C6C6C6"
20 android:pathData="M181.2,42.83V214.17a85.67,85.67 0,0 0,0 -171.34M197.93,61.6a69,69 0,0 1,0 133.8V61.6" />
21 <path
22 android:fillColor="#FFDC00"
23 android:pathData="M159.78,0a85.67,85.67 0,1 0,0 171.33ZM143.05,18.77v133.8A69,69 0,0 1,111 36.92a68.47,68.47 0,0 1,32 -18.15" />
24</vector>
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..55f62b4b9
--- /dev/null
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,86 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4SPDX-FileCopyrightText: 2023 yuzu Emulator Project
5SPDX-License-Identifier: GPL-3.0-or-later
6-->
7
8<manifest xmlns:android="http://schemas.android.com/apk/res/android">
9 <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
10 <uses-feature android:name="android.hardware.gamepad" android:required="false" />
11 <uses-feature android:name="android.software.leanback" android:required="false" />
12 <uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
13
14 <uses-permission android:name="android.permission.INTERNET" />
15 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
16 <uses-permission android:name="android.permission.NFC" />
17 <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
18
19 <application
20 android:name="org.yuzu.yuzu_emu.YuzuApplication"
21 android:label="@string/app_name_suffixed"
22 android:icon="@drawable/ic_launcher"
23 android:allowBackup="true"
24 android:hasFragileUserData="true"
25 android:supportsRtl="true"
26 android:isGame="true"
27 android:localeConfig="@xml/locales_config"
28 android:banner="@drawable/tv_banner"
29 android:extractNativeLibs="true"
30 android:fullBackupContent="@xml/data_extraction_rules"
31 android:dataExtractionRules="@xml/data_extraction_rules_api_31"
32 android:enableOnBackInvokedCallback="true">
33
34 <activity
35 android:name="org.yuzu.yuzu_emu.ui.main.MainActivity"
36 android:exported="true"
37 android:theme="@style/Theme.Yuzu.Splash.Main">
38
39 <!-- This intentfilter marks this Activity as the one that gets launched from Home screen. -->
40 <intent-filter>
41 <action android:name="android.intent.action.MAIN" />
42
43 <category android:name="android.intent.category.LAUNCHER" />
44 <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
45 </intent-filter>
46 </activity>
47
48 <activity
49 android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
50 android:theme="@style/Theme.Yuzu.Main"
51 android:label="@string/preferences_settings"/>
52
53 <activity
54 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
55 android:theme="@style/Theme.Yuzu.Main"
56 android:launchMode="singleTop"
57 android:screenOrientation="userLandscape"
58 android:exported="true">
59
60 <intent-filter>
61 <action android:name="android.nfc.action.TECH_DISCOVERED" />
62 <category android:name="android.intent.category.DEFAULT" />
63 <data android:mimeType="application/octet-stream" />
64 </intent-filter>
65
66 <meta-data
67 android:name="android.nfc.action.TECH_DISCOVERED"
68 android:resource="@xml/nfc_tech_filter" />
69 </activity>
70
71 <service android:name="org.yuzu.yuzu_emu.utils.ForegroundService"/>
72
73 <provider
74 android:name=".features.DocumentProvider"
75 android:authorities="${applicationId}.user"
76 android:grantUriPermissions="true"
77 android:exported="true"
78 android:permission="android.permission.MANAGE_DOCUMENTS">
79 <intent-filter>
80 <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
81 </intent-filter>
82 </provider>
83
84 </application>
85
86</manifest>
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
new file mode 100644
index 000000000..4be9ade14
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -0,0 +1,523 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.os.Bundle
9import android.text.Html
10import android.text.method.LinkMovementMethod
11import android.view.Surface
12import android.view.View
13import android.widget.TextView
14import androidx.annotation.Keep
15import androidx.fragment.app.DialogFragment
16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
18import org.yuzu.yuzu_emu.activities.EmulationActivity
19import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
20import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
21import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
22import org.yuzu.yuzu_emu.utils.Log.error
23import org.yuzu.yuzu_emu.utils.Log.verbose
24import org.yuzu.yuzu_emu.utils.Log.warning
25import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
26import java.lang.ref.WeakReference
27
28/**
29 * Class which contains methods that interact
30 * with the native side of the Yuzu code.
31 */
32object NativeLibrary {
33 /**
34 * Default controller id for each device
35 */
36 const val Player1Device = 0
37 const val Player2Device = 1
38 const val Player3Device = 2
39 const val Player4Device = 3
40 const val Player5Device = 4
41 const val Player6Device = 5
42 const val Player7Device = 6
43 const val Player8Device = 7
44 const val ConsoleDevice = 8
45
46 /**
47 * Controller type for each device
48 */
49 const val ProController = 3
50 const val Handheld = 4
51 const val JoyconDual = 5
52 const val JoyconLeft = 6
53 const val JoyconRight = 7
54 const val GameCube = 8
55 const val Pokeball = 9
56 const val NES = 10
57 const val SNES = 11
58 const val N64 = 12
59 const val SegaGenesis = 13
60
61 @JvmField
62 var sEmulationActivity = WeakReference<EmulationActivity?>(null)
63
64 init {
65 try {
66 System.loadLibrary("yuzu-android")
67 } catch (ex: UnsatisfiedLinkError) {
68 error("[NativeLibrary] $ex")
69 }
70 }
71
72 @Keep
73 @JvmStatic
74 fun openContentUri(path: String?, openmode: String?): Int {
75 return if (isNativePath(path!!)) {
76 YuzuApplication.documentsTree!!.openContentUri(path, openmode)
77 } else openContentUri(appContext, path, openmode)
78 }
79
80 @Keep
81 @JvmStatic
82 fun getSize(path: String?): Long {
83 return if (isNativePath(path!!)) {
84 YuzuApplication.documentsTree!!.getFileSize(path)
85 } else getFileSize(appContext, path)
86 }
87
88 /**
89 * Returns true if pro controller isn't available and handheld is
90 */
91 external fun isHandheldOnly(): Boolean
92
93 /**
94 * Changes controller type for a specific device.
95 *
96 * @param Device The input descriptor of the gamepad.
97 * @param Type The NpadStyleIndex of the gamepad.
98 */
99 external fun setDeviceType(Device: Int, Type: Int): Boolean
100
101 /**
102 * Handles event when a gamepad is connected.
103 *
104 * @param Device The input descriptor of the gamepad.
105 */
106 external fun onGamePadConnectEvent(Device: Int): Boolean
107
108 /**
109 * Handles event when a gamepad is disconnected.
110 *
111 * @param Device The input descriptor of the gamepad.
112 */
113 external fun onGamePadDisconnectEvent(Device: Int): Boolean
114
115 /**
116 * Handles button press events for a gamepad.
117 *
118 * @param Device The input descriptor of the gamepad.
119 * @param Button Key code identifying which button was pressed.
120 * @param Action Mask identifying which action is happening (button pressed down, or button released).
121 * @return If we handled the button press.
122 */
123 external fun onGamePadButtonEvent(Device: Int, Button: Int, Action: Int): Boolean
124
125 /**
126 * Handles joystick movement events.
127 *
128 * @param Device The device ID of the gamepad.
129 * @param Axis The axis ID
130 * @param x_axis The value of the x-axis represented by the given ID.
131 * @param y_axis The value of the y-axis represented by the given ID.
132 */
133 external fun onGamePadJoystickEvent(
134 Device: Int,
135 Axis: Int,
136 x_axis: Float,
137 y_axis: Float
138 ): Boolean
139
140 /**
141 * Handles motion events.
142 *
143 * @param delta_timestamp The finger id corresponding to this event
144 * @param gyro_x,gyro_y,gyro_z The value of the accelerometer sensor.
145 * @param accel_x,accel_y,accel_z The value of the y-axis
146 */
147 external fun onGamePadMotionEvent(
148 Device: Int,
149 delta_timestamp: Long,
150 gyro_x: Float,
151 gyro_y: Float,
152 gyro_z: Float,
153 accel_x: Float,
154 accel_y: Float,
155 accel_z: Float
156 ): Boolean
157
158 /**
159 * Signals and load a nfc tag
160 *
161 * @param data Byte array containing all the data from a nfc tag
162 */
163 external fun onReadNfcTag(data: ByteArray?): Boolean
164
165 /**
166 * Removes current loaded nfc tag
167 */
168 external fun onRemoveNfcTag(): Boolean
169
170 /**
171 * Handles touch press events.
172 *
173 * @param finger_id The finger id corresponding to this event
174 * @param x_axis The value of the x-axis.
175 * @param y_axis The value of the y-axis.
176 */
177 external fun onTouchPressed(finger_id: Int, x_axis: Float, y_axis: Float)
178
179 /**
180 * Handles touch movement.
181 *
182 * @param x_axis The value of the instantaneous x-axis.
183 * @param y_axis The value of the instantaneous y-axis.
184 */
185 external fun onTouchMoved(finger_id: Int, x_axis: Float, y_axis: Float)
186
187 /**
188 * Handles touch release events.
189 *
190 * @param finger_id The finger id corresponding to this event
191 */
192 external fun onTouchReleased(finger_id: Int)
193
194 external fun reloadSettings()
195
196 external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
197
198 external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
199
200 external fun initGameIni(gameID: String?)
201
202 /**
203 * Gets the embedded icon within the given ROM.
204 *
205 * @param filename the file path to the ROM.
206 * @return a byte array containing the JPEG data for the icon.
207 */
208 external fun getIcon(filename: String): ByteArray
209
210 /**
211 * Gets the embedded title of the given ISO/ROM.
212 *
213 * @param filename The file path to the ISO/ROM.
214 * @return the embedded title of the ISO/ROM.
215 */
216 external fun getTitle(filename: String): String
217
218 external fun getDescription(filename: String): String
219
220 external fun getGameId(filename: String): String
221
222 external fun getRegions(filename: String): String
223
224 external fun getCompany(filename: String): String
225
226 external fun isHomebrew(filename: String): Boolean
227
228 external fun setAppDirectory(directory: String)
229
230 external fun installFileToNand(filename: String): Int
231
232 external fun initializeGpuDriver(
233 hookLibDir: String?,
234 customDriverDir: String?,
235 customDriverName: String?,
236 fileRedirectDir: String?
237 )
238
239 external fun reloadKeys(): Boolean
240
241 external fun initializeEmulation()
242
243 external fun defaultCPUCore(): Int
244
245 /**
246 * Begins emulation.
247 */
248 external fun run(path: String?)
249
250 /**
251 * Begins emulation from the specified savestate.
252 */
253 external fun run(path: String?, savestatePath: String?, deleteSavestate: Boolean)
254
255 // Surface Handling
256 external fun surfaceChanged(surf: Surface?)
257
258 external fun surfaceDestroyed()
259
260 /**
261 * Unpauses emulation from a paused state.
262 */
263 external fun unPauseEmulation()
264
265 /**
266 * Pauses emulation.
267 */
268 external fun pauseEmulation()
269
270 /**
271 * Stops emulation.
272 */
273 external fun stopEmulation()
274
275 /**
276 * Resets the in-memory ROM metadata cache.
277 */
278 external fun resetRomMetadata()
279
280 /**
281 * Returns true if emulation is running (or is paused).
282 */
283 external fun isRunning(): Boolean
284
285 /**
286 * Returns the performance stats for the current game
287 */
288 external fun getPerfStats(): DoubleArray
289
290 /**
291 * Notifies the core emulation that the orientation has changed.
292 */
293 external fun notifyOrientationChange(layout_option: Int, rotation: Int)
294
295 enum class CoreError {
296 ErrorSystemFiles,
297 ErrorSavestate,
298 ErrorUnknown
299 }
300
301 private var coreErrorAlertResult = false
302 private val coreErrorAlertLock = Object()
303
304 class CoreErrorDialogFragment : DialogFragment() {
305 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
306 val title = requireArguments().serializable<String>("title")
307 val message = requireArguments().serializable<String>("message")
308
309 return MaterialAlertDialogBuilder(requireActivity())
310 .setTitle(title)
311 .setMessage(message)
312 .setPositiveButton(R.string.continue_button, null)
313 .setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
314 coreErrorAlertResult = false
315 synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
316 }
317 .create()
318 }
319
320 override fun onDismiss(dialog: DialogInterface) {
321 coreErrorAlertResult = true
322 synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
323 }
324
325 companion object {
326 fun newInstance(title: String?, message: String?): CoreErrorDialogFragment {
327 val frag = CoreErrorDialogFragment()
328 val args = Bundle()
329 args.putString("title", title)
330 args.putString("message", message)
331 frag.arguments = args
332 return frag
333 }
334 }
335 }
336
337 private fun onCoreErrorImpl(title: String, message: String) {
338 val emulationActivity = sEmulationActivity.get()
339 if (emulationActivity == null) {
340 error("[NativeLibrary] EmulationActivity not present")
341 return
342 }
343
344 val fragment = CoreErrorDialogFragment.newInstance(title, message)
345 fragment.show(emulationActivity.supportFragmentManager, "coreError")
346 }
347
348 /**
349 * Handles a core error.
350 *
351 * @return true: continue; false: abort
352 */
353 fun onCoreError(error: CoreError?, details: String): Boolean {
354 val emulationActivity = sEmulationActivity.get()
355 if (emulationActivity == null) {
356 error("[NativeLibrary] EmulationActivity not present")
357 return false
358 }
359
360 val title: String
361 val message: String
362 when (error) {
363 CoreError.ErrorSystemFiles -> {
364 title = emulationActivity.getString(R.string.system_archive_not_found)
365 message = emulationActivity.getString(
366 R.string.system_archive_not_found_message,
367 details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
368 )
369 }
370 CoreError.ErrorSavestate -> {
371 title = emulationActivity.getString(R.string.save_load_error)
372 message = details
373 }
374 CoreError.ErrorUnknown -> {
375 title = emulationActivity.getString(R.string.fatal_error)
376 message = emulationActivity.getString(R.string.fatal_error_message)
377 }
378 else -> {
379 return true
380 }
381 }
382
383 // Show the AlertDialog on the main thread.
384 emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
385
386 // Wait for the lock to notify that it is complete.
387 synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
388
389 return coreErrorAlertResult
390 }
391
392 @Keep
393 @JvmStatic
394 fun exitEmulationActivity(resultCode: Int) {
395 val Success = 0
396 val ErrorNotInitialized = 1
397 val ErrorGetLoader = 2
398 val ErrorSystemFiles = 3
399 val ErrorSharedFont = 4
400 val ErrorVideoCore = 5
401 val ErrorUnknown = 6
402 val ErrorLoader = 7
403
404 val captionId: Int
405 var descriptionId: Int
406 when (resultCode) {
407 ErrorVideoCore -> {
408 captionId = R.string.loader_error_video_core
409 descriptionId = R.string.loader_error_video_core_description
410 }
411 else -> {
412 captionId = R.string.loader_error_encrypted
413 descriptionId = R.string.loader_error_encrypted_roms_description
414 if (!reloadKeys()) {
415 descriptionId = R.string.loader_error_encrypted_keys_description
416 }
417 }
418 }
419
420 val emulationActivity = sEmulationActivity.get()
421 if (emulationActivity == null) {
422 warning("[NativeLibrary] EmulationActivity is null, can't exit.")
423 return
424 }
425
426 val builder = MaterialAlertDialogBuilder(emulationActivity)
427 .setTitle(captionId)
428 .setMessage(
429 Html.fromHtml(
430 emulationActivity.getString(descriptionId),
431 Html.FROM_HTML_MODE_LEGACY
432 )
433 )
434 .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() }
435 .setOnDismissListener { emulationActivity.finish() }
436 emulationActivity.runOnUiThread {
437 val alert = builder.create()
438 alert.show()
439 (alert.findViewById<View>(android.R.id.message) as TextView).movementMethod =
440 LinkMovementMethod.getInstance()
441 }
442 }
443
444 fun setEmulationActivity(emulationActivity: EmulationActivity?) {
445 verbose("[NativeLibrary] Registering EmulationActivity.")
446 sEmulationActivity = WeakReference(emulationActivity)
447 }
448
449 fun clearEmulationActivity() {
450 verbose("[NativeLibrary] Unregistering EmulationActivity.")
451 sEmulationActivity.clear()
452 }
453
454 /**
455 * Logs the Yuzu version, Android version and, CPU.
456 */
457 external fun logDeviceInfo()
458
459 /**
460 * Submits inline keyboard text. Called on input for buttons that result text.
461 * @param text Text to submit to the inline software keyboard implementation.
462 */
463 external fun submitInlineKeyboardText(text: String?)
464
465 /**
466 * Submits inline keyboard input. Used to indicate keys pressed that are not text.
467 * @param key_code Android Key Code associated with the keyboard input.
468 */
469 external fun submitInlineKeyboardInput(key_code: Int)
470
471 /**
472 * Button type for use in onTouchEvent
473 */
474 object ButtonType {
475 const val BUTTON_A = 0
476 const val BUTTON_B = 1
477 const val BUTTON_X = 2
478 const val BUTTON_Y = 3
479 const val STICK_L = 4
480 const val STICK_R = 5
481 const val TRIGGER_L = 6
482 const val TRIGGER_R = 7
483 const val TRIGGER_ZL = 8
484 const val TRIGGER_ZR = 9
485 const val BUTTON_PLUS = 10
486 const val BUTTON_MINUS = 11
487 const val DPAD_LEFT = 12
488 const val DPAD_UP = 13
489 const val DPAD_RIGHT = 14
490 const val DPAD_DOWN = 15
491 const val BUTTON_SL = 16
492 const val BUTTON_SR = 17
493 const val BUTTON_HOME = 18
494 const val BUTTON_CAPTURE = 19
495 }
496
497 /**
498 * Stick type for use in onTouchEvent
499 */
500 object StickType {
501 const val STICK_L = 0
502 const val STICK_R = 1
503 }
504
505 /**
506 * Button states
507 */
508 object ButtonState {
509 const val RELEASED = 0
510 const val PRESSED = 1
511 }
512
513 /**
514 * Result from installFileToNand
515 */
516 object InstallFileToNandResult {
517 const val Success = 0
518 const val SuccessFileOverwritten = 1
519 const val Error = 2
520 const val ErrorBaseGame = 3
521 const val ErrorFilenameExtension = 4
522 }
523}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
new file mode 100644
index 000000000..4c947b786
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu
5
6import android.app.Application
7import android.app.NotificationChannel
8import android.app.NotificationManager
9import android.content.Context
10import org.yuzu.yuzu_emu.utils.DirectoryInitialization
11import org.yuzu.yuzu_emu.utils.DocumentsTree
12import org.yuzu.yuzu_emu.utils.GpuDriverHelper
13import java.io.File
14
15fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
16
17class YuzuApplication : Application() {
18 private fun createNotificationChannels() {
19 val emulationChannel = NotificationChannel(
20 getString(R.string.emulation_notification_channel_id),
21 getString(R.string.emulation_notification_channel_name),
22 NotificationManager.IMPORTANCE_LOW
23 )
24 emulationChannel.description = getString(R.string.emulation_notification_channel_description)
25 emulationChannel.setSound(null, null)
26 emulationChannel.vibrationPattern = null
27
28 val noticeChannel = NotificationChannel(
29 getString(R.string.notice_notification_channel_id),
30 getString(R.string.notice_notification_channel_name),
31 NotificationManager.IMPORTANCE_HIGH
32 )
33 noticeChannel.description = getString(R.string.notice_notification_channel_description)
34 noticeChannel.setSound(null, null)
35
36 // Register the channel with the system; you can't change the importance
37 // or other notification behaviors after this
38 val notificationManager = getSystemService(NotificationManager::class.java)
39 notificationManager.createNotificationChannel(emulationChannel)
40 notificationManager.createNotificationChannel(noticeChannel)
41 }
42
43 override fun onCreate() {
44 super.onCreate()
45 application = this
46 documentsTree = DocumentsTree()
47 DirectoryInitialization.start(applicationContext)
48 GpuDriverHelper.initializeDriverParameters(applicationContext)
49 NativeLibrary.logDeviceInfo()
50
51 createNotificationChannels();
52 }
53
54 companion object {
55 var documentsTree: DocumentsTree? = null
56 lateinit var application: YuzuApplication
57
58 val appContext: Context
59 get() = application.applicationContext
60 }
61}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
new file mode 100644
index 000000000..20a0394f5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -0,0 +1,306 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.activities
5
6import android.app.Activity
7import android.content.Context
8import android.content.Intent
9import android.graphics.Rect
10import android.hardware.Sensor
11import android.hardware.SensorEvent
12import android.hardware.SensorEventListener
13import android.hardware.SensorManager
14import android.os.Bundle
15import android.view.InputDevice
16import android.view.KeyEvent
17import android.view.MotionEvent
18import android.view.Surface
19import android.view.View
20import android.view.inputmethod.InputMethodManager
21import androidx.activity.viewModels
22import androidx.appcompat.app.AppCompatActivity
23import androidx.core.view.WindowCompat
24import androidx.core.view.WindowInsetsCompat
25import androidx.core.view.WindowInsetsControllerCompat
26import androidx.lifecycle.Lifecycle
27import androidx.lifecycle.lifecycleScope
28import androidx.lifecycle.repeatOnLifecycle
29import androidx.window.layout.WindowInfoTracker
30import kotlinx.coroutines.Dispatchers
31import kotlinx.coroutines.launch
32import org.yuzu.yuzu_emu.NativeLibrary
33import org.yuzu.yuzu_emu.R
34import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
35import org.yuzu.yuzu_emu.fragments.EmulationFragment
36import org.yuzu.yuzu_emu.model.Game
37import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
38import org.yuzu.yuzu_emu.utils.ForegroundService
39import org.yuzu.yuzu_emu.utils.InputHandler
40import org.yuzu.yuzu_emu.utils.NfcReader
41import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
42import org.yuzu.yuzu_emu.utils.ThemeHelper
43import kotlin.math.roundToInt
44
45class EmulationActivity : AppCompatActivity(), SensorEventListener {
46 private var controllerMappingHelper: ControllerMappingHelper? = null
47
48 var isActivityRecreated = false
49 private var emulationFragment: EmulationFragment? = null
50 private lateinit var nfcReader: NfcReader
51 private lateinit var inputHandler: InputHandler
52
53 private val gyro = FloatArray(3)
54 private val accel = FloatArray(3)
55 private var motionTimestamp: Long = 0
56 private var flipMotionOrientation: Boolean = false
57
58 private lateinit var game: Game
59
60 private val settingsViewModel: SettingsViewModel by viewModels()
61
62 override fun onDestroy() {
63 stopForegroundService(this)
64 super.onDestroy()
65 }
66
67 override fun onCreate(savedInstanceState: Bundle?) {
68 ThemeHelper.setTheme(this)
69
70 settingsViewModel.settings.loadSettings()
71
72 super.onCreate(savedInstanceState)
73 if (savedInstanceState == null) {
74 // Get params we were passed
75 game = intent.parcelable(EXTRA_SELECTED_GAME)!!
76 isActivityRecreated = false
77 } else {
78 isActivityRecreated = true
79 restoreState(savedInstanceState)
80 }
81 controllerMappingHelper = ControllerMappingHelper()
82
83 // Set these options now so that the SurfaceView the game renders into is the right size.
84 enableFullscreenImmersive()
85
86 setContentView(R.layout.activity_emulation)
87 window.decorView.setBackgroundColor(getColor(android.R.color.black))
88
89 // Find or create the EmulationFragment
90 emulationFragment =
91 supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment?
92 if (emulationFragment == null) {
93 emulationFragment = EmulationFragment.newInstance(game)
94 supportFragmentManager.beginTransaction()
95 .add(R.id.frame_emulation_fragment, emulationFragment!!)
96 .commit()
97 }
98 title = game.title
99
100 nfcReader = NfcReader(this)
101 nfcReader.initialize()
102
103 inputHandler = InputHandler()
104 inputHandler.initialize()
105
106 lifecycleScope.launch(Dispatchers.Main) {
107 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
108 WindowInfoTracker.getOrCreate(this@EmulationActivity)
109 .windowLayoutInfo(this@EmulationActivity)
110 .collect { emulationFragment?.updateCurrentLayout(this@EmulationActivity, it) }
111 }
112 }
113
114 // Start a foreground service to prevent the app from getting killed in the background
115 val startIntent = Intent(this, ForegroundService::class.java)
116 startForegroundService(startIntent)
117 }
118
119 override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
120 if (event.action == KeyEvent.ACTION_DOWN) {
121 if (keyCode == KeyEvent.KEYCODE_ENTER) {
122 // Special case, we do not support multiline input, dismiss the keyboard.
123 val overlayView: View =
124 this.findViewById(R.id.surface_input_overlay)
125 val im =
126 overlayView.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
127 im.hideSoftInputFromWindow(overlayView.windowToken, 0)
128 } else {
129 val textChar = event.unicodeChar
130 if (textChar == 0) {
131 // No text, button input.
132 NativeLibrary.submitInlineKeyboardInput(keyCode)
133 } else {
134 // Text submitted.
135 NativeLibrary.submitInlineKeyboardText(textChar.toChar().toString())
136 }
137 }
138 }
139 return super.onKeyDown(keyCode, event)
140 }
141
142 override fun onResume() {
143 super.onResume()
144 nfcReader.startScanning()
145 startMotionSensorListener()
146 }
147
148 override fun onPause() {
149 super.onPause()
150 nfcReader.stopScanning()
151 stopMotionSensorListener()
152 }
153
154 override fun onNewIntent(intent: Intent) {
155 super.onNewIntent(intent)
156 setIntent(intent)
157 nfcReader.onNewIntent(intent)
158 }
159
160 override fun onSaveInstanceState(outState: Bundle) {
161 outState.putParcelable(EXTRA_SELECTED_GAME, game)
162 super.onSaveInstanceState(outState)
163 }
164
165 override fun dispatchKeyEvent(event: KeyEvent): Boolean {
166 if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
167 event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
168 ) {
169 return super.dispatchKeyEvent(event)
170 }
171
172 return inputHandler.dispatchKeyEvent(event)
173 }
174
175 override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
176 if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
177 event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
178 ) {
179 return super.dispatchGenericMotionEvent(event)
180 }
181
182 // Don't attempt to do anything if we are disconnecting a device.
183 if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
184 return true
185 }
186
187 return inputHandler.dispatchGenericMotionEvent(event)
188 }
189
190 override fun onSensorChanged(event: SensorEvent) {
191 val rotation = this.display?.rotation
192 if (rotation == Surface.ROTATION_90) {
193 flipMotionOrientation = true
194 }
195 if (rotation == Surface.ROTATION_270) {
196 flipMotionOrientation = false
197 }
198
199 if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
200 if (flipMotionOrientation) {
201 accel[0] = event.values[1] / SensorManager.GRAVITY_EARTH
202 accel[1] = -event.values[0] / SensorManager.GRAVITY_EARTH
203 } else {
204 accel[0] = -event.values[1] / SensorManager.GRAVITY_EARTH
205 accel[1] = event.values[0] / SensorManager.GRAVITY_EARTH
206 }
207 accel[2] = -event.values[2] / SensorManager.GRAVITY_EARTH
208 }
209 if (event.sensor.type == Sensor.TYPE_GYROSCOPE) {
210 // Investigate why sensor value is off by 6x
211 if (flipMotionOrientation) {
212 gyro[0] = -event.values[1] / 6.0f
213 gyro[1] = event.values[0] / 6.0f
214 } else {
215 gyro[0] = event.values[1] / 6.0f
216 gyro[1] = -event.values[0] / 6.0f
217 }
218 gyro[2] = event.values[2] / 6.0f
219 }
220
221 // Only update state on accelerometer data
222 if (event.sensor.type != Sensor.TYPE_ACCELEROMETER) {
223 return
224 }
225 val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000
226 motionTimestamp = event.timestamp
227 NativeLibrary.onGamePadMotionEvent(
228 NativeLibrary.Player1Device,
229 deltaTimestamp,
230 gyro[0],
231 gyro[1],
232 gyro[2],
233 accel[0],
234 accel[1],
235 accel[2]
236 )
237 NativeLibrary.onGamePadMotionEvent(
238 NativeLibrary.ConsoleDevice,
239 deltaTimestamp,
240 gyro[0],
241 gyro[1],
242 gyro[2],
243 accel[0],
244 accel[1],
245 accel[2]
246 )
247 }
248
249 override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
250
251 private fun restoreState(savedInstanceState: Bundle) {
252 game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
253 }
254
255 private fun enableFullscreenImmersive() {
256 WindowCompat.setDecorFitsSystemWindows(window, false)
257
258 WindowInsetsControllerCompat(window, window.decorView).let { controller ->
259 controller.hide(WindowInsetsCompat.Type.systemBars())
260 controller.systemBarsBehavior =
261 WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
262 }
263 }
264
265 private fun startMotionSensorListener() {
266 val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
267 val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
268 val accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
269 sensorManager.registerListener(this, gyroSensor, SensorManager.SENSOR_DELAY_GAME)
270 sensorManager.registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME)
271 }
272
273 private fun stopMotionSensorListener() {
274 val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
275 val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
276 val accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
277
278 sensorManager.unregisterListener(this, gyroSensor)
279 sensorManager.unregisterListener(this, accelSensor)
280 }
281
282 companion object {
283 const val EXTRA_SELECTED_GAME = "SelectedGame"
284
285 fun launch(activity: AppCompatActivity, game: Game) {
286 val launcher = Intent(activity, EmulationActivity::class.java)
287 launcher.putExtra(EXTRA_SELECTED_GAME, game)
288 activity.startActivity(launcher)
289 }
290
291 fun stopForegroundService(activity: Activity) {
292 val startIntent = Intent(activity, ForegroundService::class.java)
293 startIntent.action = ForegroundService.ACTION_STOP
294 activity.startForegroundService(startIntent)
295 }
296
297 private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean {
298 if (view == null) {
299 return true
300 }
301 val viewBounds = Rect()
302 view.getGlobalVisibleRect(viewBounds)
303 return !viewBounds.contains(x.roundToInt(), y.roundToInt())
304 }
305 }
306}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
new file mode 100644
index 000000000..7f9e2e2d4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -0,0 +1,134 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.graphics.Bitmap
7import android.graphics.BitmapFactory
8import android.net.Uri
9import android.text.TextUtils
10import android.view.LayoutInflater
11import android.view.View
12import android.view.ViewGroup
13import android.widget.ImageView
14import android.widget.Toast
15import androidx.appcompat.app.AppCompatActivity
16import androidx.documentfile.provider.DocumentFile
17import androidx.lifecycle.ViewModelProvider
18import androidx.lifecycle.lifecycleScope
19import androidx.preference.PreferenceManager
20import androidx.recyclerview.widget.AsyncDifferConfig
21import androidx.recyclerview.widget.DiffUtil
22import androidx.recyclerview.widget.ListAdapter
23import androidx.recyclerview.widget.RecyclerView
24import coil.load
25import kotlinx.coroutines.launch
26import org.yuzu.yuzu_emu.NativeLibrary
27import org.yuzu.yuzu_emu.R
28import org.yuzu.yuzu_emu.YuzuApplication
29import org.yuzu.yuzu_emu.databinding.CardGameBinding
30import org.yuzu.yuzu_emu.activities.EmulationActivity
31import org.yuzu.yuzu_emu.model.Game
32import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
33import org.yuzu.yuzu_emu.model.GamesViewModel
34
35class GameAdapter(private val activity: AppCompatActivity) :
36 ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
37 View.OnClickListener {
38 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
39 // Create a new view.
40 val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
41 binding.cardGame.setOnClickListener(this)
42
43 // Use that view to create a ViewHolder.
44 return GameViewHolder(binding)
45 }
46
47 override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
48 holder.bind(currentList[position])
49 }
50
51 override fun getItemCount(): Int = currentList.size
52
53 /**
54 * Launches the game that was clicked on.
55 *
56 * @param view The card representing the game the user wants to play.
57 */
58 override fun onClick(view: View) {
59 val holder = view.tag as GameViewHolder
60
61 val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true
62 if (!gameExists) {
63 Toast.makeText(
64 YuzuApplication.appContext,
65 R.string.loader_error_file_not_found,
66 Toast.LENGTH_LONG
67 ).show()
68
69 ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
70 return
71 }
72
73 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
74 preferences.edit()
75 .putLong(
76 holder.game.keyLastPlayedTime,
77 System.currentTimeMillis()
78 )
79 .apply()
80
81 EmulationActivity.launch(activity, holder.game)
82 }
83
84 inner class GameViewHolder(val binding: CardGameBinding) :
85 RecyclerView.ViewHolder(binding.root) {
86 lateinit var game: Game
87
88 init {
89 binding.cardGame.tag = this
90 }
91
92 fun bind(game: Game) {
93 this.game = game
94
95 binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
96 activity.lifecycleScope.launch {
97 val bitmap = decodeGameIcon(game.path)
98 binding.imageGameScreen.load(bitmap) {
99 error(R.drawable.default_icon)
100 }
101 }
102
103 binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
104
105 binding.textGameTitle.postDelayed(
106 {
107 binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
108 binding.textGameTitle.isSelected = true
109 },
110 3000
111 )
112 }
113 }
114
115 private class DiffCallback : DiffUtil.ItemCallback<Game>() {
116 override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
117 return oldItem.gameId == newItem.gameId
118 }
119
120 override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
121 return oldItem == newItem
122 }
123 }
124
125 private fun decodeGameIcon(uri: String): Bitmap? {
126 val data = NativeLibrary.getIcon(uri)
127 return BitmapFactory.decodeByteArray(
128 data,
129 0,
130 data.size,
131 BitmapFactory.Options()
132 )
133 }
134}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
new file mode 100644
index 000000000..b719dd539
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -0,0 +1,69 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.view.LayoutInflater
7import android.view.View
8import android.view.ViewGroup
9import androidx.appcompat.app.AppCompatActivity
10import androidx.core.content.ContextCompat
11import androidx.core.content.res.ResourcesCompat
12import androidx.recyclerview.widget.RecyclerView
13import org.yuzu.yuzu_emu.R
14import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
15import org.yuzu.yuzu_emu.model.HomeSetting
16
17class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) :
18 RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
19 View.OnClickListener {
20 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
21 val binding =
22 CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
23 binding.root.setOnClickListener(this)
24 return HomeOptionViewHolder(binding)
25 }
26
27 override fun getItemCount(): Int {
28 return options.size
29 }
30
31 override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
32 holder.bind(options[position])
33 }
34
35 override fun onClick(view: View) {
36 val holder = view.tag as HomeOptionViewHolder
37 holder.option.onClick.invoke()
38 }
39
40 inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
41 RecyclerView.ViewHolder(binding.root) {
42 lateinit var option: HomeSetting
43
44 init {
45 itemView.tag = this
46 }
47
48 fun bind(option: HomeSetting) {
49 this.option = option
50 binding.optionTitle.text = activity.resources.getString(option.titleId)
51 binding.optionDescription.text = activity.resources.getString(option.descriptionId)
52 binding.optionIcon.setImageDrawable(
53 ResourcesCompat.getDrawable(
54 activity.resources,
55 option.iconId,
56 activity.theme
57 )
58 )
59
60 when (option.titleId) {
61 R.string.get_early_access -> binding.optionLayout.background =
62 ContextCompat.getDrawable(
63 binding.optionCard.context,
64 R.drawable.premium_background
65 )
66 }
67 }
68 }
69}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
new file mode 100644
index 000000000..7006651d0
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
@@ -0,0 +1,54 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.view.LayoutInflater
7import android.view.View
8import android.view.ViewGroup
9import androidx.appcompat.app.AppCompatActivity
10import androidx.recyclerview.widget.RecyclerView
11import androidx.recyclerview.widget.RecyclerView.ViewHolder
12import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
14import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
15import org.yuzu.yuzu_emu.model.License
16
17class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) :
18 RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(),
19 View.OnClickListener {
20 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
21 val binding =
22 ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
23 binding.root.setOnClickListener(this)
24 return LicenseViewHolder(binding)
25 }
26
27 override fun getItemCount(): Int = licenses.size
28
29 override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) {
30 holder.bind(licenses[position])
31 }
32
33 override fun onClick(view: View) {
34 val license = (view.tag as LicenseViewHolder).license
35 LicenseBottomSheetDialogFragment.newInstance(license)
36 .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
37 }
38
39 inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) {
40 lateinit var license: License
41
42 init {
43 itemView.tag = this
44 }
45
46 fun bind(license: License) {
47 this.license = license
48
49 val context = YuzuApplication.appContext
50 binding.textSettingName.text = context.getString(license.titleId)
51 binding.textSettingDescription.text = context.getString(license.descriptionId)
52 }
53 }
54}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
new file mode 100644
index 000000000..481ddd5a5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.text.Html
7import android.view.LayoutInflater
8import android.view.ViewGroup
9import androidx.appcompat.app.AppCompatActivity
10import androidx.core.content.res.ResourcesCompat
11import androidx.recyclerview.widget.RecyclerView
12import com.google.android.material.button.MaterialButton
13import org.yuzu.yuzu_emu.databinding.PageSetupBinding
14import org.yuzu.yuzu_emu.model.SetupPage
15
16class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
17 RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
18 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
19 val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
20 return SetupPageViewHolder(binding)
21 }
22
23 override fun getItemCount(): Int = pages.size
24
25 override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) =
26 holder.bind(pages[position])
27
28 inner class SetupPageViewHolder(val binding: PageSetupBinding) :
29 RecyclerView.ViewHolder(binding.root) {
30 lateinit var page: SetupPage
31
32 init {
33 itemView.tag = this
34 }
35
36 fun bind(page: SetupPage) {
37 this.page = page
38 binding.icon.setImageDrawable(
39 ResourcesCompat.getDrawable(
40 activity.resources,
41 page.iconId,
42 activity.theme
43 )
44 )
45 binding.textTitle.text = activity.resources.getString(page.titleId)
46 binding.textDescription.text =
47 Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
48
49 binding.buttonAction.apply {
50 text = activity.resources.getString(page.buttonTextId)
51 if (page.buttonIconId != 0) {
52 icon = ResourcesCompat.getDrawable(
53 activity.resources,
54 page.buttonIconId,
55 activity.theme
56 )
57 }
58 iconGravity =
59 if (page.leftAlignedIcon) {
60 MaterialButton.ICON_GRAVITY_START
61 } else {
62 MaterialButton.ICON_GRAVITY_END
63 }
64 setOnClickListener {
65 page.buttonAction.invoke()
66 }
67 }
68 }
69 }
70}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt
new file mode 100644
index 000000000..82a6712b6
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt
@@ -0,0 +1,121 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.applets.keyboard
5
6import android.content.Context
7import android.os.Handler
8import android.os.Looper
9import android.view.KeyEvent
10import android.view.View
11import android.view.WindowInsets
12import android.view.inputmethod.InputMethodManager
13import androidx.annotation.Keep
14import androidx.core.view.ViewCompat
15import org.yuzu.yuzu_emu.NativeLibrary
16import org.yuzu.yuzu_emu.R
17import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment
18import java.io.Serializable
19
20@Keep
21object SoftwareKeyboard {
22 lateinit var data: KeyboardData
23 val dataLock = Object()
24
25 private fun executeNormalImpl(config: KeyboardConfig) {
26 val emulationActivity = NativeLibrary.sEmulationActivity.get()
27 data = KeyboardData(SwkbdResult.Cancel.ordinal, "")
28 val fragment = KeyboardDialogFragment.newInstance(config)
29 fragment.show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG)
30 }
31
32 private fun executeInlineImpl(config: KeyboardConfig) {
33 val emulationActivity = NativeLibrary.sEmulationActivity.get()
34
35 val overlayView = emulationActivity!!.findViewById<View>(R.id.surface_input_overlay)
36 val im =
37 overlayView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
38 im.showSoftInput(overlayView, InputMethodManager.SHOW_FORCED)
39
40 // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result.
41 val handler = Handler(Looper.myLooper()!!)
42 val delayMs = 500
43 handler.postDelayed(object : Runnable {
44 override fun run() {
45 val insets = ViewCompat.getRootWindowInsets(overlayView)
46 val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
47 if (isKeyboardVisible) {
48 handler.postDelayed(this, delayMs.toLong())
49 return
50 }
51
52 // No longer visible, submit the result.
53 NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
54 }
55 }, delayMs.toLong())
56 }
57
58 @JvmStatic
59 fun executeNormal(config: KeyboardConfig): KeyboardData {
60 NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeNormalImpl(config) }
61 synchronized(dataLock) {
62 dataLock.wait()
63 }
64 return data
65 }
66
67 @JvmStatic
68 fun executeInline(config: KeyboardConfig) {
69 NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeInlineImpl(config) }
70 }
71
72 // Corresponds to Service::AM::Applets::SwkbdType
73 enum class SwkbdType {
74 Normal,
75 NumberPad,
76 Qwerty,
77 Unknown3,
78 Latin,
79 SimplifiedChinese,
80 TraditionalChinese,
81 Korean
82 }
83
84 // Corresponds to Service::AM::Applets::SwkbdPasswordMode
85 enum class SwkbdPasswordMode {
86 Disabled,
87 Enabled
88 }
89
90 // Corresponds to Service::AM::Applets::SwkbdResult
91 enum class SwkbdResult {
92 Ok,
93 Cancel
94 }
95
96 @Keep
97 data class KeyboardConfig(
98 var ok_text: String? = null,
99 var header_text: String? = null,
100 var sub_text: String? = null,
101 var guide_text: String? = null,
102 var initial_text: String? = null,
103 var left_optional_symbol_key: Short = 0,
104 var right_optional_symbol_key: Short = 0,
105 var max_text_length: Int = 0,
106 var min_text_length: Int = 0,
107 var initial_cursor_position: Int = 0,
108 var type: Int = 0,
109 var password_mode: Int = 0,
110 var text_draw_type: Int = 0,
111 var key_disable_flags: Int = 0,
112 var use_blur_background: Boolean = false,
113 var enable_backspace_button: Boolean = false,
114 var enable_return_button: Boolean = false,
115 var disable_cancel_button: Boolean = false
116 ) : Serializable
117
118 // Corresponds to Frontend::KeyboardData
119 @Keep
120 data class KeyboardData(var result: Int, var text: String)
121}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/ui/KeyboardDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/ui/KeyboardDialogFragment.kt
new file mode 100644
index 000000000..607a3d506
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/ui/KeyboardDialogFragment.kt
@@ -0,0 +1,100 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.applets.keyboard.ui
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.os.Bundle
9import android.text.InputFilter
10import android.text.InputType
11import androidx.fragment.app.DialogFragment
12import com.google.android.material.dialog.MaterialAlertDialogBuilder
13import org.yuzu.yuzu_emu.R
14import org.yuzu.yuzu_emu.applets.keyboard.SoftwareKeyboard
15import org.yuzu.yuzu_emu.applets.keyboard.SoftwareKeyboard.KeyboardConfig
16import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
17import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
18
19class KeyboardDialogFragment : DialogFragment() {
20 private lateinit var binding: DialogEditTextBinding
21 private lateinit var config: KeyboardConfig
22
23 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
24 binding = DialogEditTextBinding.inflate(layoutInflater)
25 config = requireArguments().serializable(CONFIG)!!
26
27 // Set up the input
28 binding.editText.hint = config.initial_text
29 binding.editText.isSingleLine = !config.enable_return_button
30 binding.editText.filters =
31 arrayOf<InputFilter>(InputFilter.LengthFilter(config.max_text_length))
32
33 // Handle input type
34 var inputType: Int
35 when (config.type) {
36 SoftwareKeyboard.SwkbdType.Normal.ordinal,
37 SoftwareKeyboard.SwkbdType.Qwerty.ordinal,
38 SoftwareKeyboard.SwkbdType.Unknown3.ordinal,
39 SoftwareKeyboard.SwkbdType.Latin.ordinal,
40 SoftwareKeyboard.SwkbdType.SimplifiedChinese.ordinal,
41 SoftwareKeyboard.SwkbdType.TraditionalChinese.ordinal,
42 SoftwareKeyboard.SwkbdType.Korean.ordinal -> {
43 inputType = InputType.TYPE_CLASS_TEXT
44 if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) {
45 inputType = inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
46 }
47 }
48 SoftwareKeyboard.SwkbdType.NumberPad.ordinal -> {
49 inputType = InputType.TYPE_CLASS_NUMBER
50 if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) {
51 inputType = inputType or InputType.TYPE_NUMBER_VARIATION_PASSWORD
52 }
53 }
54 else -> {
55 inputType = InputType.TYPE_CLASS_TEXT
56 if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) {
57 inputType = inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
58 }
59 }
60 }
61 binding.editText.inputType = inputType
62
63 val headerText =
64 config.header_text!!.ifEmpty { resources.getString(R.string.software_keyboard) }
65 val okText =
66 config.ok_text!!.ifEmpty { resources.getString(R.string.submit) }
67
68 return MaterialAlertDialogBuilder(requireContext())
69 .setTitle(headerText)
70 .setView(binding.root)
71 .setPositiveButton(okText) { _, _ ->
72 SoftwareKeyboard.data.result = SoftwareKeyboard.SwkbdResult.Ok.ordinal
73 SoftwareKeyboard.data.text = binding.editText.text.toString()
74 }
75 .setNegativeButton(resources.getString(android.R.string.cancel)) { _, _ ->
76 SoftwareKeyboard.data.result = SoftwareKeyboard.SwkbdResult.Cancel.ordinal
77 }
78 .create()
79 }
80
81 override fun onDismiss(dialog: DialogInterface) {
82 super.onDismiss(dialog)
83 synchronized(SoftwareKeyboard.dataLock) {
84 SoftwareKeyboard.dataLock.notifyAll()
85 }
86 }
87
88 companion object {
89 const val TAG = "KeyboardDialogFragment"
90 const val CONFIG = "keyboard_config"
91
92 fun newInstance(config: KeyboardConfig?): KeyboardDialogFragment {
93 val frag = KeyboardDialogFragment()
94 val args = Bundle()
95 args.putSerializable(CONFIG, config)
96 frag.arguments = args
97 return frag
98 }
99 }
100}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt
new file mode 100644
index 000000000..3b1559c80
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.disk_shader_cache
5
6import androidx.annotation.Keep
7import org.yuzu.yuzu_emu.NativeLibrary
8import org.yuzu.yuzu_emu.R
9import org.yuzu.yuzu_emu.disk_shader_cache.ui.ShaderProgressDialogFragment
10
11@Keep
12object DiskShaderCacheProgress {
13 val finishLock = Object()
14 private lateinit var fragment: ShaderProgressDialogFragment
15
16 private fun prepareDialog() {
17 val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
18 emulationActivity.runOnUiThread {
19 fragment = ShaderProgressDialogFragment.newInstance(
20 emulationActivity.getString(R.string.loading),
21 emulationActivity.getString(R.string.preparing_shaders)
22 )
23 fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG)
24 }
25 synchronized(finishLock) { finishLock.wait() }
26 }
27
28 @JvmStatic
29 fun loadProgress(stage: Int, progress: Int, max: Int) {
30 val emulationActivity = NativeLibrary.sEmulationActivity.get()
31 ?: error("[DiskShaderCacheProgress] EmulationActivity not present")
32
33 when (LoadCallbackStage.values()[stage]) {
34 LoadCallbackStage.Prepare -> prepareDialog()
35 LoadCallbackStage.Build -> fragment.onUpdateProgress(
36 emulationActivity.getString(R.string.building_shaders),
37 progress,
38 max
39 )
40 LoadCallbackStage.Complete -> fragment.dismiss()
41 }
42 }
43
44 // Equivalent to VideoCore::LoadCallbackStage
45 enum class LoadCallbackStage {
46 Prepare, Build, Complete
47 }
48}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt
new file mode 100644
index 000000000..bf6f0366d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt
@@ -0,0 +1,31 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.disk_shader_cache
5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel
9
10class ShaderProgressViewModel : ViewModel() {
11 private val _progress = MutableLiveData(0)
12 val progress: LiveData<Int> get() = _progress
13
14 private val _max = MutableLiveData(0)
15 val max: LiveData<Int> get() = _max
16
17 private val _message = MutableLiveData("")
18 val message: LiveData<String> get() = _message
19
20 fun setProgress(progress: Int) {
21 _progress.postValue(progress)
22 }
23
24 fun setMax(max: Int) {
25 _max.postValue(max)
26 }
27
28 fun setMessage(msg: String) {
29 _message.postValue(msg)
30 }
31}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt
new file mode 100644
index 000000000..2c68c9ac3
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt
@@ -0,0 +1,101 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.disk_shader_cache.ui
5
6import android.app.Dialog
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import androidx.appcompat.app.AlertDialog
12import androidx.fragment.app.DialogFragment
13import androidx.lifecycle.ViewModelProvider
14import com.google.android.material.dialog.MaterialAlertDialogBuilder
15import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
16import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress
17import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel
18
19class ShaderProgressDialogFragment : DialogFragment() {
20 private var _binding: DialogProgressBarBinding? = null
21 private val binding get() = _binding!!
22
23 private lateinit var alertDialog: AlertDialog
24
25 private lateinit var shaderProgressViewModel: ShaderProgressViewModel
26
27 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
28 _binding = DialogProgressBarBinding.inflate(layoutInflater)
29 shaderProgressViewModel =
30 ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java]
31
32 val title = requireArguments().getString(TITLE)
33 val message = requireArguments().getString(MESSAGE)
34
35 isCancelable = false
36 alertDialog = MaterialAlertDialogBuilder(requireActivity())
37 .setView(binding.root)
38 .setTitle(title)
39 .setMessage(message)
40 .create()
41 return alertDialog
42 }
43
44 override fun onCreateView(
45 inflater: LayoutInflater,
46 container: ViewGroup?,
47 savedInstanceState: Bundle?
48 ): View {
49 return binding.root
50 }
51
52 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
53 super.onViewCreated(view, savedInstanceState)
54 shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress ->
55 binding.progressBar.progress = progress
56 setUpdateText()
57 }
58 shaderProgressViewModel.max.observe(viewLifecycleOwner) { max ->
59 binding.progressBar.max = max
60 setUpdateText()
61 }
62 shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
63 alertDialog.setMessage(msg)
64 }
65 synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() }
66 }
67
68 override fun onDestroyView() {
69 super.onDestroyView()
70 _binding = null
71 }
72
73 fun onUpdateProgress(msg: String, progress: Int, max: Int) {
74 shaderProgressViewModel.setProgress(progress)
75 shaderProgressViewModel.setMax(max)
76 shaderProgressViewModel.setMessage(msg)
77 }
78
79 private fun setUpdateText() {
80 binding.progressText.text = String.format(
81 "%d/%d",
82 shaderProgressViewModel.progress.value,
83 shaderProgressViewModel.max.value
84 )
85 }
86
87 companion object {
88 const val TAG = "ProgressDialogFragment"
89 const val TITLE = "title"
90 const val MESSAGE = "message"
91
92 fun newInstance(title: String, message: String): ShaderProgressDialogFragment {
93 val frag = ShaderProgressDialogFragment()
94 val args = Bundle()
95 args.putString(TITLE, title)
96 args.putString(MESSAGE, message)
97 frag.arguments = args
98 return frag
99 }
100 }
101}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt
new file mode 100644
index 000000000..4c3a9ca80
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt
@@ -0,0 +1,302 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// SPDX-License-Identifier: MPL-2.0
5// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
6
7package org.yuzu.yuzu_emu.features
8
9import android.database.Cursor
10import android.database.MatrixCursor
11import android.os.CancellationSignal
12import android.os.ParcelFileDescriptor
13import android.provider.DocumentsContract
14import android.provider.DocumentsProvider
15import android.webkit.MimeTypeMap
16import org.yuzu.yuzu_emu.BuildConfig
17import org.yuzu.yuzu_emu.R
18import org.yuzu.yuzu_emu.YuzuApplication
19import org.yuzu.yuzu_emu.getPublicFilesDir
20import java.io.*
21
22class DocumentProvider : DocumentsProvider() {
23 private val baseDirectory: File
24 get() = File(YuzuApplication.application.getPublicFilesDir().canonicalPath)
25
26 companion object {
27 private val DEFAULT_ROOT_PROJECTION: Array<String> = arrayOf(
28 DocumentsContract.Root.COLUMN_ROOT_ID,
29 DocumentsContract.Root.COLUMN_MIME_TYPES,
30 DocumentsContract.Root.COLUMN_FLAGS,
31 DocumentsContract.Root.COLUMN_ICON,
32 DocumentsContract.Root.COLUMN_TITLE,
33 DocumentsContract.Root.COLUMN_SUMMARY,
34 DocumentsContract.Root.COLUMN_DOCUMENT_ID,
35 DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
36 )
37
38 private val DEFAULT_DOCUMENT_PROJECTION: Array<String> = arrayOf(
39 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
40 DocumentsContract.Document.COLUMN_MIME_TYPE,
41 DocumentsContract.Document.COLUMN_DISPLAY_NAME,
42 DocumentsContract.Document.COLUMN_LAST_MODIFIED,
43 DocumentsContract.Document.COLUMN_FLAGS,
44 DocumentsContract.Document.COLUMN_SIZE
45 )
46
47 const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user"
48 const val ROOT_ID: String = "root"
49 }
50
51 override fun onCreate(): Boolean {
52 return true
53 }
54
55 /**
56 * @return The [File] that corresponds to the document ID supplied by [getDocumentId]
57 */
58 private fun getFile(documentId: String): File {
59 if (documentId.startsWith(ROOT_ID)) {
60 val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
61 if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found")
62 return file
63 } else {
64 throw FileNotFoundException("'$documentId' is not in any known root")
65 }
66 }
67
68 /**
69 * @return A unique ID for the provided [File]
70 */
71 private fun getDocumentId(file: File): String {
72 return "$ROOT_ID/${file.toRelativeString(baseDirectory)}"
73 }
74
75 override fun queryRoots(projection: Array<out String>?): Cursor {
76 val cursor = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
77
78 cursor.newRow().apply {
79 add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT_ID)
80 add(DocumentsContract.Root.COLUMN_SUMMARY, null)
81 add(
82 DocumentsContract.Root.COLUMN_FLAGS,
83 DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
84 )
85 add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name))
86 add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
87 add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*")
88 add(DocumentsContract.Root.COLUMN_AVAILABLE_BYTES, baseDirectory.freeSpace)
89 add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu)
90 }
91
92 return cursor
93 }
94
95 override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
96 val cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
97 return includeFile(cursor, documentId, null)
98 }
99
100 override fun isChildDocument(parentDocumentId: String?, documentId: String?): Boolean {
101 return documentId?.startsWith(parentDocumentId!!) ?: false
102 }
103
104 /**
105 * @return A new [File] with a unique name based off the supplied [name], not conflicting with any existing file
106 */
107 private fun File.resolveWithoutConflict(name: String): File {
108 var file = resolve(name)
109 if (file.exists()) {
110 var noConflictId =
111 1 // Makes sure two files don't have the same name by adding a number to the end
112 val extension = name.substringAfterLast('.')
113 val baseName = name.substringBeforeLast('.')
114 while (file.exists())
115 file = resolve("$baseName (${noConflictId++}).$extension")
116 }
117 return file
118 }
119
120 override fun createDocument(
121 parentDocumentId: String?,
122 mimeType: String?,
123 displayName: String
124 ): String {
125 val parentFile = getFile(parentDocumentId!!)
126 val newFile = parentFile.resolveWithoutConflict(displayName)
127
128 try {
129 if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {
130 if (!newFile.mkdir())
131 throw IOException("Failed to create directory")
132 } else {
133 if (!newFile.createNewFile())
134 throw IOException("Failed to create file")
135 }
136 } catch (e: IOException) {
137 throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
138 }
139
140 return getDocumentId(newFile)
141 }
142
143 override fun deleteDocument(documentId: String?) {
144 val file = getFile(documentId!!)
145 if (!file.delete())
146 throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
147 }
148
149 override fun removeDocument(documentId: String, parentDocumentId: String?) {
150 val parent = getFile(parentDocumentId!!)
151 val file = getFile(documentId)
152
153 if (parent == file || file.parentFile == null || file.parentFile!! == parent) {
154 if (!file.delete())
155 throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
156 } else {
157 throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
158 }
159 }
160
161 override fun renameDocument(documentId: String?, displayName: String?): String {
162 if (displayName == null)
163 throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
164
165 val sourceFile = getFile(documentId!!)
166 val sourceParentFile = sourceFile.parentFile
167 ?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent")
168 val destFile = sourceParentFile.resolve(displayName)
169
170 try {
171 if (!sourceFile.renameTo(destFile))
172 throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'")
173 } catch (e: Exception) {
174 throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}")
175 }
176
177 return getDocumentId(destFile)
178 }
179
180 private fun copyDocument(
181 sourceDocumentId: String, sourceParentDocumentId: String,
182 targetParentDocumentId: String?
183 ): String {
184 if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
185 throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
186
187 return copyDocument(sourceDocumentId, targetParentDocumentId)
188 }
189
190 override fun copyDocument(sourceDocumentId: String, targetParentDocumentId: String?): String {
191 val parent = getFile(targetParentDocumentId!!)
192 val oldFile = getFile(sourceDocumentId)
193 val newFile = parent.resolveWithoutConflict(oldFile.name)
194
195 try {
196 if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true)))
197 throw IOException("Couldn't create new file")
198
199 FileInputStream(oldFile).use { inStream ->
200 FileOutputStream(newFile).use { outStream ->
201 inStream.copyTo(outStream)
202 }
203 }
204 } catch (e: IOException) {
205 throw FileNotFoundException("Couldn't copy document '$sourceDocumentId': ${e.message}")
206 }
207
208 return getDocumentId(newFile)
209 }
210
211 override fun moveDocument(
212 sourceDocumentId: String, sourceParentDocumentId: String?,
213 targetParentDocumentId: String?
214 ): String {
215 try {
216 val newDocumentId = copyDocument(
217 sourceDocumentId, sourceParentDocumentId!!,
218 targetParentDocumentId
219 )
220 removeDocument(sourceDocumentId, sourceParentDocumentId)
221 return newDocumentId
222 } catch (e: FileNotFoundException) {
223 throw FileNotFoundException("Couldn't move document '$sourceDocumentId'")
224 }
225 }
226
227 private fun includeFile(cursor: MatrixCursor, documentId: String?, file: File?): MatrixCursor {
228 val localDocumentId = documentId ?: file?.let { getDocumentId(it) }
229 val localFile = file ?: getFile(documentId!!)
230
231 var flags = 0
232 if (localFile.isDirectory && localFile.canWrite()) {
233 flags = DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE
234 } else if (localFile.canWrite()) {
235 flags = DocumentsContract.Document.FLAG_SUPPORTS_WRITE
236 flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_DELETE
237
238 flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_REMOVE
239 flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_MOVE
240 flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_COPY
241 flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_RENAME
242 }
243
244 cursor.newRow().apply {
245 add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
246 add(
247 DocumentsContract.Document.COLUMN_DISPLAY_NAME,
248 if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name
249 )
250 add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
251 add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
252 add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
253 add(DocumentsContract.Document.COLUMN_FLAGS, flags)
254 if (localFile == baseDirectory)
255 add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu)
256 }
257
258 return cursor
259 }
260
261 private fun getTypeForFile(file: File): Any {
262 return if (file.isDirectory)
263 DocumentsContract.Document.MIME_TYPE_DIR
264 else
265 getTypeForName(file.name)
266 }
267
268 private fun getTypeForName(name: String): Any {
269 val lastDot = name.lastIndexOf('.')
270 if (lastDot >= 0) {
271 val extension = name.substring(lastDot + 1)
272 val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
273 if (mime != null)
274 return mime
275 }
276 return "application/octect-stream"
277 }
278
279 override fun queryChildDocuments(
280 parentDocumentId: String?,
281 projection: Array<out String>?,
282 sortOrder: String?
283 ): Cursor {
284 var cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
285
286 val parent = getFile(parentDocumentId!!)
287 for (file in parent.listFiles()!!)
288 cursor = includeFile(cursor, null, file)
289
290 return cursor
291 }
292
293 override fun openDocument(
294 documentId: String?,
295 mode: String?,
296 signal: CancellationSignal?
297 ): ParcelFileDescriptor {
298 val file = documentId?.let { getFile(it) }
299 val accessMode = ParcelFileDescriptor.parseMode(mode)
300 return ParcelFileDescriptor.open(file, accessMode)
301 }
302}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
new file mode 100644
index 000000000..a6e9833ee
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
@@ -0,0 +1,8 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractBooleanSetting : AbstractSetting {
7 var boolean: Boolean
8}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
new file mode 100644
index 000000000..6fe4bc263
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
@@ -0,0 +1,8 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractFloatSetting : AbstractSetting {
7 var float: Float
8}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
new file mode 100644
index 000000000..892b7dcfe
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
@@ -0,0 +1,8 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractIntSetting : AbstractSetting {
7 var int: Int
8}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
new file mode 100644
index 000000000..258580209
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
@@ -0,0 +1,12 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractSetting {
7 val key: String?
8 val section: String?
9 val isRuntimeEditable: Boolean
10 val valueAsString: String
11 val defaultValue: Any
12}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
new file mode 100644
index 000000000..0d02c5997
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
@@ -0,0 +1,8 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractStringSetting : AbstractSetting {
7 var string: String
8}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
new file mode 100644
index 000000000..3dfd66779
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6enum class BooleanSetting(
7 override val key: String,
8 override val section: String,
9 override val defaultValue: Boolean
10) : AbstractBooleanSetting {
11 USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
12
13 override var boolean: Boolean = defaultValue
14
15 override val valueAsString: String
16 get() = boolean.toString()
17
18 override val isRuntimeEditable: Boolean
19 get() {
20 for (setting in NOT_RUNTIME_EDITABLE) {
21 if (setting == this) {
22 return false
23 }
24 }
25 return true
26 }
27
28 companion object {
29 private val NOT_RUNTIME_EDITABLE = listOf(
30 USE_CUSTOM_RTC
31 )
32
33 fun from(key: String): BooleanSetting? =
34 BooleanSetting.values().firstOrNull { it.key == key }
35
36 fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
37 }
38}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
new file mode 100644
index 000000000..e5545a916
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6enum class FloatSetting(
7 override val key: String,
8 override val section: String,
9 override val defaultValue: Float
10) : AbstractFloatSetting {
11 // No float settings currently exist
12 EMPTY_SETTING("", "", 0f);
13
14 override var float: Float = defaultValue
15
16 override val valueAsString: String
17 get() = float.toString()
18
19 override val isRuntimeEditable: Boolean
20 get() {
21 for (setting in NOT_RUNTIME_EDITABLE) {
22 if (setting == this) {
23 return false
24 }
25 }
26 return true
27 }
28
29 companion object {
30 private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
31
32 fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
33
34 fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
35 }
36}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
new file mode 100644
index 000000000..fa84f94f5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -0,0 +1,136 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6enum class IntSetting(
7 override val key: String,
8 override val section: String,
9 override val defaultValue: Int
10) : AbstractIntSetting {
11 RENDERER_USE_SPEED_LIMIT(
12 "use_speed_limit",
13 Settings.SECTION_RENDERER,
14 1
15 ),
16 USE_DOCKED_MODE(
17 "use_docked_mode",
18 Settings.SECTION_SYSTEM,
19 0
20 ),
21 RENDERER_USE_DISK_SHADER_CACHE(
22 "use_disk_shader_cache",
23 Settings.SECTION_RENDERER,
24 1
25 ),
26 RENDERER_FORCE_MAX_CLOCK(
27 "force_max_clock",
28 Settings.SECTION_RENDERER,
29 0
30 ),
31 RENDERER_ASYNCHRONOUS_SHADERS(
32 "use_asynchronous_shaders",
33 Settings.SECTION_RENDERER,
34 0
35 ),
36 RENDERER_REACTIVE_FLUSHING(
37 "use_reactive_flushing",
38 Settings.SECTION_RENDERER,
39 0
40 ),
41 RENDERER_DEBUG(
42 "debug",
43 Settings.SECTION_RENDERER,
44 0
45 ),
46 RENDERER_SPEED_LIMIT(
47 "speed_limit",
48 Settings.SECTION_RENDERER,
49 100
50 ),
51 CPU_ACCURACY(
52 "cpu_accuracy",
53 Settings.SECTION_CPU,
54 0
55 ),
56 REGION_INDEX(
57 "region_index",
58 Settings.SECTION_SYSTEM,
59 -1
60 ),
61 LANGUAGE_INDEX(
62 "language_index",
63 Settings.SECTION_SYSTEM,
64 1
65 ),
66 RENDERER_BACKEND(
67 "backend",
68 Settings.SECTION_RENDERER,
69 1
70 ),
71 RENDERER_ACCURACY(
72 "gpu_accuracy",
73 Settings.SECTION_RENDERER,
74 0
75 ),
76 RENDERER_RESOLUTION(
77 "resolution_setup",
78 Settings.SECTION_RENDERER,
79 2
80 ),
81 RENDERER_VSYNC(
82 "use_vsync",
83 Settings.SECTION_RENDERER,
84 0
85 ),
86 RENDERER_SCALING_FILTER(
87 "scaling_filter",
88 Settings.SECTION_RENDERER,
89 1
90 ),
91 RENDERER_ANTI_ALIASING(
92 "anti_aliasing",
93 Settings.SECTION_RENDERER,
94 0
95 ),
96 RENDERER_ASPECT_RATIO(
97 "aspect_ratio",
98 Settings.SECTION_RENDERER,
99 0
100 ),
101 AUDIO_VOLUME(
102 "volume",
103 Settings.SECTION_AUDIO,
104 100
105 );
106
107 override var int: Int = defaultValue
108
109 override val valueAsString: String
110 get() = int.toString()
111
112 override val isRuntimeEditable: Boolean
113 get() {
114 for (setting in NOT_RUNTIME_EDITABLE) {
115 if (setting == this) {
116 return false
117 }
118 }
119 return true
120 }
121
122 companion object {
123 private val NOT_RUNTIME_EDITABLE = listOf(
124 RENDERER_USE_DISK_SHADER_CACHE,
125 RENDERER_ASYNCHRONOUS_SHADERS,
126 RENDERER_DEBUG,
127 RENDERER_BACKEND,
128 RENDERER_RESOLUTION,
129 RENDERER_VSYNC
130 )
131
132 fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
133
134 fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
135 }
136}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
new file mode 100644
index 000000000..474f598a9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
@@ -0,0 +1,37 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6/**
7 * A semantically-related group of Settings objects. These Settings are
8 * internally stored as a HashMap.
9 */
10class SettingSection(val name: String) {
11 val settings = HashMap<String, AbstractSetting>()
12
13 /**
14 * Convenience method; inserts a value directly into the backing HashMap.
15 *
16 * @param setting The Setting to be inserted.
17 */
18 fun putSetting(setting: AbstractSetting) {
19 settings[setting.key!!] = setting
20 }
21
22 /**
23 * Convenience method; gets a value directly from the backing HashMap.
24 *
25 * @param key Used to retrieve the Setting.
26 * @return A Setting object (you should probably cast this before using)
27 */
28 fun getSetting(key: String): AbstractSetting? {
29 return settings[key]
30 }
31
32 fun mergeSection(settingSection: SettingSection) {
33 for (setting in settingSection.settings.values) {
34 putSetting(setting)
35 }
36 }
37}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
new file mode 100644
index 000000000..8df20b928
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -0,0 +1,158 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6import android.text.TextUtils
7import org.yuzu.yuzu_emu.R
8import org.yuzu.yuzu_emu.YuzuApplication
9import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
10import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
11import java.util.*
12
13class Settings {
14 private var gameId: String? = null
15
16 var isLoaded = false
17
18 /**
19 * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
20 * when getting a key not already in the map
21 */
22 class SettingsSectionMap : HashMap<String, SettingSection?>() {
23 override operator fun get(key: String): SettingSection? {
24 if (!super.containsKey(key)) {
25 val section = SettingSection(key)
26 super.put(key, section)
27 return section
28 }
29 return super.get(key)
30 }
31 }
32
33 var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
34
35 fun getSection(sectionName: String): SettingSection? {
36 return sections[sectionName]
37 }
38
39 val isEmpty: Boolean
40 get() = sections.isEmpty()
41
42 fun loadSettings(view: SettingsActivityView? = null) {
43 sections = SettingsSectionMap()
44 loadYuzuSettings(view)
45 if (!TextUtils.isEmpty(gameId)) {
46 loadCustomGameSettings(gameId!!, view)
47 }
48 isLoaded = true
49 }
50
51 private fun loadYuzuSettings(view: SettingsActivityView?) {
52 for ((fileName) in configFileSectionsMap) {
53 sections.putAll(SettingsFile.readFile(fileName, view))
54 }
55 }
56
57 private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
58 // Custom game settings
59 mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
60 }
61
62 private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
63 for ((key, updatedSection) in updatedSections) {
64 if (sections.containsKey(key)) {
65 val originalSection = sections[key]
66 originalSection!!.mergeSection(updatedSection!!)
67 } else {
68 sections[key] = updatedSection
69 }
70 }
71 }
72
73 fun loadSettings(gameId: String, view: SettingsActivityView) {
74 this.gameId = gameId
75 loadSettings(view)
76 }
77
78 fun saveSettings(view: SettingsActivityView) {
79 if (TextUtils.isEmpty(gameId)) {
80 view.showToastMessage(
81 YuzuApplication.appContext.getString(R.string.ini_saved),
82 false
83 )
84
85 for ((fileName, sectionNames) in configFileSectionsMap) {
86 val iniSections = TreeMap<String, SettingSection>()
87 for (section in sectionNames) {
88 iniSections[section] = sections[section]!!
89 }
90
91 SettingsFile.saveFile(fileName, iniSections, view)
92 }
93 } else {
94 // Custom game settings
95 view.showToastMessage(
96 YuzuApplication.appContext.getString(R.string.gameid_saved, gameId),
97 false
98 )
99
100 SettingsFile.saveCustomGameSettings(gameId, sections)
101 }
102 }
103
104 companion object {
105 const val SECTION_GENERAL = "General"
106 const val SECTION_SYSTEM = "System"
107 const val SECTION_RENDERER = "Renderer"
108 const val SECTION_AUDIO = "Audio"
109 const val SECTION_CPU = "Cpu"
110 const val SECTION_THEME = "Theme"
111 const val SECTION_DEBUG = "Debug"
112
113 const val PREF_OVERLAY_INIT = "OverlayInit"
114 const val PREF_CONTROL_SCALE = "controlScale"
115 const val PREF_CONTROL_OPACITY = "controlOpacity"
116 const val PREF_TOUCH_ENABLED = "isTouchEnabled"
117 const val PREF_BUTTON_TOGGLE_0 = "buttonToggle0"
118 const val PREF_BUTTON_TOGGLE_1 = "buttonToggle1"
119 const val PREF_BUTTON_TOGGLE_2 = "buttonToggle2"
120 const val PREF_BUTTON_TOGGLE_3 = "buttonToggle3"
121 const val PREF_BUTTON_TOGGLE_4 = "buttonToggle4"
122 const val PREF_BUTTON_TOGGLE_5 = "buttonToggle5"
123 const val PREF_BUTTON_TOGGLE_6 = "buttonToggle6"
124 const val PREF_BUTTON_TOGGLE_7 = "buttonToggle7"
125 const val PREF_BUTTON_TOGGLE_8 = "buttonToggle8"
126 const val PREF_BUTTON_TOGGLE_9 = "buttonToggle9"
127 const val PREF_BUTTON_TOGGLE_10 = "buttonToggle10"
128 const val PREF_BUTTON_TOGGLE_11 = "buttonToggle11"
129 const val PREF_BUTTON_TOGGLE_12 = "buttonToggle12"
130 const val PREF_BUTTON_TOGGLE_13 = "buttonToggle13"
131 const val PREF_BUTTON_TOGGLE_14 = "buttonToggle14"
132
133 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
134 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
135 const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
136 const val PREF_MENU_SETTINGS_LANDSCAPE = "EmulationMenuSettings_LandscapeScreenLayout"
137 const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
138 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
139
140 const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
141 const val PREF_THEME = "Theme"
142 const val PREF_THEME_MODE = "ThemeMode"
143 const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
144
145 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
146
147 init {
148 configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
149 listOf(
150 SECTION_GENERAL,
151 SECTION_SYSTEM,
152 SECTION_RENDERER,
153 SECTION_AUDIO,
154 SECTION_CPU
155 )
156 }
157 }
158}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt
new file mode 100644
index 000000000..bd9233d62
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt
@@ -0,0 +1,10 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6import androidx.lifecycle.ViewModel
7
8class SettingsViewModel : ViewModel() {
9 val settings = Settings()
10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
new file mode 100644
index 000000000..63f95690c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
@@ -0,0 +1,37 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6enum class StringSetting(
7 override val key: String,
8 override val section: String,
9 override val defaultValue: String
10) : AbstractStringSetting {
11 CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
12
13 override var string: String = defaultValue
14
15 override val valueAsString: String
16 get() = string
17
18 override val isRuntimeEditable: Boolean
19 get() {
20 for (setting in NOT_RUNTIME_EDITABLE) {
21 if (setting == this) {
22 return false
23 }
24 }
25 return true
26 }
27
28 companion object {
29 private val NOT_RUNTIME_EDITABLE = listOf(
30 CUSTOM_RTC
31 )
32
33 fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
34
35 fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
36 }
37}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
new file mode 100644
index 000000000..bc0bf7788
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
@@ -0,0 +1,31 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
8
9class DateTimeSetting(
10 setting: AbstractSetting?,
11 titleId: Int,
12 descriptionId: Int,
13 val key: String? = null,
14 private val defaultValue: String? = null
15) : SettingsItem(setting, titleId, descriptionId) {
16 override val type = TYPE_DATETIME_SETTING
17
18 val value: String
19 get() = if (setting != null) {
20 val setting = setting as AbstractStringSetting
21 setting.string
22 } else {
23 defaultValue!!
24 }
25
26 fun setSelectedValue(datetime: String): AbstractStringSetting {
27 val stringSetting = setting as AbstractStringSetting
28 stringSetting.string = datetime
29 return stringSetting
30 }
31}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
new file mode 100644
index 000000000..0f8edbfb0
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
@@ -0,0 +1,14 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7
8class HeaderSetting(
9 setting: AbstractSetting?,
10 titleId: Int,
11 descriptionId: Int
12) : SettingsItem(setting, titleId, descriptionId) {
13 override val type = TYPE_HEADER
14}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
new file mode 100644
index 000000000..caaab50d8
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
@@ -0,0 +1,13 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6class RunnableSetting(
7 titleId: Int,
8 descriptionId: Int,
9 val isRuntimeRunnable: Boolean,
10 val runnable: () -> Unit
11) : SettingsItem(null, titleId, descriptionId) {
12 override val type = TYPE_RUNNABLE
13}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
new file mode 100644
index 000000000..07520849e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -0,0 +1,39 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6import org.yuzu.yuzu_emu.NativeLibrary
7import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
8
9/**
10 * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
11 * Each one corresponds to a [AbstractSetting] object, so this class's subclasses
12 * should vaguely correspond to those subclasses. There are a few with multiple analogues
13 * and a few with none (Headers, for example, do not correspond to anything in the ini
14 * file.)
15 */
16abstract class SettingsItem(
17 var setting: AbstractSetting?,
18 val nameId: Int,
19 val descriptionId: Int
20) {
21 abstract val type: Int
22
23 val isEditable: Boolean
24 get() {
25 if (!NativeLibrary.isRunning()) return true
26 return setting?.isRuntimeEditable ?: false
27 }
28
29 companion object {
30 const val TYPE_HEADER = 0
31 const val TYPE_SWITCH = 1
32 const val TYPE_SINGLE_CHOICE = 2
33 const val TYPE_SLIDER = 3
34 const val TYPE_SUBMENU = 4
35 const val TYPE_STRING_SINGLE_CHOICE = 5
36 const val TYPE_DATETIME_SETTING = 6
37 const val TYPE_RUNNABLE = 7
38 }
39}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
new file mode 100644
index 000000000..9eac9904e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
@@ -0,0 +1,40 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
7import org.yuzu.yuzu_emu.features.settings.model.IntSetting
8
9class SingleChoiceSetting(
10 setting: AbstractIntSetting?,
11 titleId: Int,
12 descriptionId: Int,
13 val choicesId: Int,
14 val valuesId: Int,
15 val key: String? = null,
16 val defaultValue: Int? = null
17) : SettingsItem(setting, titleId, descriptionId) {
18 override val type = TYPE_SINGLE_CHOICE
19
20 val selectedValue: Int
21 get() = if (setting != null) {
22 val setting = setting as AbstractIntSetting
23 setting.int
24 } else {
25 defaultValue!!
26 }
27
28 /**
29 * Write a value to the backing int. If that int was previously null,
30 * initializes a new one and returns it, so it can be added to the Hashmap.
31 *
32 * @param selection New value of the int.
33 * @return the existing setting with the new value applied.
34 */
35 fun setSelectedValue(selection: Int): AbstractIntSetting {
36 val intSetting = setting as AbstractIntSetting
37 intSetting.int = selection
38 return intSetting
39 }
40}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
new file mode 100644
index 000000000..842648ce4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
@@ -0,0 +1,64 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
8import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
9import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
10import org.yuzu.yuzu_emu.features.settings.model.IntSetting
11import org.yuzu.yuzu_emu.utils.Log
12import kotlin.math.roundToInt
13
14class SliderSetting(
15 setting: AbstractSetting?,
16 titleId: Int,
17 descriptionId: Int,
18 val min: Int,
19 val max: Int,
20 val units: String,
21 val key: String? = null,
22 val defaultValue: Int? = null,
23) : SettingsItem(setting, titleId, descriptionId) {
24 override val type = TYPE_SLIDER
25
26 val selectedValue: Int
27 get() {
28 val setting = setting ?: return defaultValue!!
29 return when (setting) {
30 is AbstractIntSetting -> setting.int
31 is AbstractFloatSetting -> setting.float.roundToInt()
32 else -> {
33 Log.error("[SliderSetting] Error casting setting type.")
34 -1
35 }
36 }
37 }
38
39 /**
40 * Write a value to the backing int. If that int was previously null,
41 * initializes a new one and returns it, so it can be added to the Hashmap.
42 *
43 * @param selection New value of the int.
44 * @return the existing setting with the new value applied.
45 */
46 fun setSelectedValue(selection: Int): AbstractIntSetting {
47 val intSetting = setting as AbstractIntSetting
48 intSetting.int = selection
49 return intSetting
50 }
51
52 /**
53 * Write a value to the backing float. If that float was previously null,
54 * initializes a new one and returns it, so it can be added to the Hashmap.
55 *
56 * @param selection New value of the float.
57 * @return the existing setting with the new value applied.
58 */
59 fun setSelectedValue(selection: Float): AbstractFloatSetting {
60 val floatSetting = setting as AbstractFloatSetting
61 floatSetting.float = selection
62 return floatSetting
63 }
64}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
new file mode 100644
index 000000000..9e9b00d10
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
@@ -0,0 +1,58 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
8import org.yuzu.yuzu_emu.features.settings.model.StringSetting
9
10class StringSingleChoiceSetting(
11 val key: String? = null,
12 setting: AbstractSetting?,
13 titleId: Int,
14 descriptionId: Int,
15 val choicesId: Array<String>,
16 private val valuesId: Array<String>?,
17 private val defaultValue: String? = null
18) : SettingsItem(setting, titleId, descriptionId) {
19 override val type = TYPE_STRING_SINGLE_CHOICE
20
21 fun getValueAt(index: Int): String? {
22 if (valuesId == null) return null
23 return if (index >= 0 && index < valuesId.size) {
24 valuesId[index]
25 } else ""
26 }
27
28 val selectedValue: String
29 get() = if (setting != null) {
30 val setting = setting as AbstractStringSetting
31 setting.string
32 } else {
33 defaultValue!!
34 }
35 val selectValueIndex: Int
36 get() {
37 val selectedValue = selectedValue
38 for (i in valuesId!!.indices) {
39 if (valuesId[i] == selectedValue) {
40 return i
41 }
42 }
43 return -1
44 }
45
46 /**
47 * Write a value to the backing int. If that int was previously null,
48 * initializes a new one and returns it, so it can be added to the Hashmap.
49 *
50 * @param selection New value of the int.
51 * @return the existing setting with the new value applied.
52 */
53 fun setSelectedValue(selection: String): AbstractStringSetting {
54 val stringSetting = setting as AbstractStringSetting
55 stringSetting.string = selection
56 return stringSetting
57 }
58}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
new file mode 100644
index 000000000..a3ef59c2f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -0,0 +1,14 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7
8class SubmenuSetting(
9 titleId: Int,
10 descriptionId: Int,
11 val menuKey: String
12) : SettingsItem(null, titleId, descriptionId) {
13 override val type = TYPE_SUBMENU
14}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
new file mode 100644
index 000000000..90b198718
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
@@ -0,0 +1,62 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model.view
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
8import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
9
10class SwitchSetting(
11 setting: AbstractSetting,
12 titleId: Int,
13 descriptionId: Int,
14 val key: String? = null,
15 val defaultValue: Any? = null
16) : SettingsItem(setting, titleId, descriptionId) {
17 override val type = TYPE_SWITCH
18
19 val isChecked: Boolean
20 get() {
21 if (setting == null) {
22 return defaultValue as Boolean
23 }
24
25 // Try integer setting
26 try {
27 val setting = setting as AbstractIntSetting
28 return setting.int == 1
29 } catch (_: ClassCastException) {
30 }
31
32 // Try boolean setting
33 try {
34 val setting = setting as AbstractBooleanSetting
35 return setting.boolean
36 } catch (_: ClassCastException) {
37 }
38 return defaultValue as Boolean
39 }
40
41 /**
42 * Write a value to the backing boolean. If that boolean was previously null,
43 * initializes a new one and returns it, so it can be added to the Hashmap.
44 *
45 * @param checked Pretty self explanatory.
46 * @return the existing setting with the new value applied.
47 */
48 fun setChecked(checked: Boolean): AbstractSetting {
49 // Try integer setting
50 try {
51 val setting = setting as AbstractIntSetting
52 setting.int = if (checked) 1 else 0
53 return setting
54 } catch (_: ClassCastException) {
55 }
56
57 // Try boolean setting
58 val setting = setting as AbstractBooleanSetting
59 setting.boolean = checked
60 return setting
61 }
62}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
new file mode 100644
index 000000000..72e2cce2a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -0,0 +1,243 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import android.content.Context
7import android.content.Intent
8import android.os.Bundle
9import android.view.Menu
10import android.view.View
11import android.widget.Toast
12import androidx.activity.viewModels
13import androidx.appcompat.app.AppCompatActivity
14import androidx.core.view.ViewCompat
15import androidx.core.view.WindowCompat
16import androidx.core.view.WindowInsetsCompat
17import android.view.ViewGroup.MarginLayoutParams
18import androidx.activity.OnBackPressedCallback
19import androidx.core.view.updatePadding
20import com.google.android.material.color.MaterialColors
21import org.yuzu.yuzu_emu.NativeLibrary
22import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
24import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
25import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
26import org.yuzu.yuzu_emu.features.settings.model.IntSetting
27import org.yuzu.yuzu_emu.features.settings.model.Settings
28import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
29import org.yuzu.yuzu_emu.features.settings.model.StringSetting
30import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
31import org.yuzu.yuzu_emu.utils.*
32import java.io.IOException
33
34class SettingsActivity : AppCompatActivity(), SettingsActivityView {
35 private val presenter = SettingsActivityPresenter(this)
36
37 private lateinit var binding: ActivitySettingsBinding
38
39 private val settingsViewModel: SettingsViewModel by viewModels()
40
41 override val settings: Settings get() = settingsViewModel.settings
42
43 override fun onCreate(savedInstanceState: Bundle?) {
44 ThemeHelper.setTheme(this)
45
46 super.onCreate(savedInstanceState)
47
48 binding = ActivitySettingsBinding.inflate(layoutInflater)
49 setContentView(binding.root)
50
51 WindowCompat.setDecorFitsSystemWindows(window, false)
52
53 val launcher = intent
54 val gameID = launcher.getStringExtra(ARG_GAME_ID)
55 val menuTag = launcher.getStringExtra(ARG_MENU_TAG)
56 presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
57
58 // Show "Back" button in the action bar for navigation
59 setSupportActionBar(binding.toolbarSettings)
60 supportActionBar!!.setDisplayHomeAsUpEnabled(true)
61
62 if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
63 binding.navigationBarShade.setBackgroundColor(
64 ThemeHelper.getColorWithOpacity(
65 MaterialColors.getColor(
66 binding.navigationBarShade,
67 com.google.android.material.R.attr.colorSurface
68 ),
69 ThemeHelper.SYSTEM_BAR_ALPHA
70 )
71 )
72 }
73
74 onBackPressedDispatcher.addCallback(
75 this,
76 object : OnBackPressedCallback(true) {
77 override fun handleOnBackPressed() = navigateBack()
78 })
79
80 setInsets()
81 }
82
83 override fun onSupportNavigateUp(): Boolean {
84 navigateBack()
85 return true
86 }
87
88 private fun navigateBack() {
89 if (supportFragmentManager.backStackEntryCount > 0) {
90 supportFragmentManager.popBackStack()
91 } else {
92 finish()
93 }
94 }
95
96 override fun onCreateOptionsMenu(menu: Menu): Boolean {
97 val inflater = menuInflater
98 inflater.inflate(R.menu.menu_settings, menu)
99 return true
100 }
101
102 override fun onSaveInstanceState(outState: Bundle) {
103 // Critical: If super method is not called, rotations will be busted.
104 super.onSaveInstanceState(outState)
105 presenter.saveState(outState)
106 }
107
108 override fun onStart() {
109 super.onStart()
110 presenter.onStart()
111 }
112
113 /**
114 * If this is called, the user has left the settings screen (potentially through the
115 * home button) and will expect their changes to be persisted. So we kick off an
116 * IntentService which will do so on a background thread.
117 */
118 override fun onStop() {
119 super.onStop()
120 presenter.onStop(isFinishing)
121 }
122
123 override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
124 if (!addToStack && settingsFragment != null) {
125 return
126 }
127
128 val transaction = supportFragmentManager.beginTransaction()
129 if (addToStack) {
130 if (areSystemAnimationsEnabled()) {
131 transaction.setCustomAnimations(
132 R.anim.anim_settings_fragment_in,
133 R.anim.anim_settings_fragment_out,
134 0,
135 R.anim.anim_pop_settings_fragment_out
136 )
137 }
138 transaction.addToBackStack(null)
139 }
140 transaction.replace(
141 R.id.frame_content,
142 SettingsFragment.newInstance(menuTag, gameId),
143 FRAGMENT_TAG
144 )
145 transaction.commit()
146 }
147
148 private fun areSystemAnimationsEnabled(): Boolean {
149 val duration = android.provider.Settings.Global.getFloat(
150 contentResolver,
151 android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f
152 )
153 val transition = android.provider.Settings.Global.getFloat(
154 contentResolver,
155 android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f
156 )
157 return duration != 0f && transition != 0f
158 }
159
160 override fun onSettingsFileLoaded() {
161 val fragment: SettingsFragmentView? = settingsFragment
162 fragment?.loadSettingsList()
163 }
164
165 override fun onSettingsFileNotFound() {
166 val fragment: SettingsFragmentView? = settingsFragment
167 fragment?.loadSettingsList()
168 }
169
170 override fun showToastMessage(message: String, is_long: Boolean) {
171 Toast.makeText(
172 this,
173 message,
174 if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
175 ).show()
176 }
177
178 override fun onSettingChanged() {
179 presenter.onSettingChanged()
180 }
181
182 fun onSettingsReset() {
183 // Prevents saving to a non-existent settings file
184 presenter.onSettingsReset()
185
186 // Reset the static memory representation of each setting
187 BooleanSetting.clear()
188 FloatSetting.clear()
189 IntSetting.clear()
190 StringSetting.clear()
191
192 // Delete settings file because the user may have changed values that do not exist in the UI
193 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
194 if (!settingsFile.delete()) {
195 throw IOException("Failed to delete $settingsFile")
196 }
197
198 showToastMessage(getString(R.string.settings_reset), true)
199 finish()
200 }
201
202 fun setToolbarTitle(title: String) {
203 binding.toolbarSettingsLayout.title = title
204 }
205
206 private val settingsFragment: SettingsFragment?
207 get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
208
209 private fun setInsets() {
210 ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat ->
211 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
212 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
213 view.updatePadding(
214 left = barInsets.left + cutoutInsets.left,
215 right = barInsets.right + cutoutInsets.right
216 )
217
218 val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
219 mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
220 mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
221 binding.appbarSettings.layoutParams = mlpAppBar
222
223 val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
224 mlpShade.height = barInsets.bottom
225 binding.navigationBarShade.layoutParams = mlpShade
226
227 windowInsets
228 }
229 }
230
231 companion object {
232 private const val ARG_MENU_TAG = "menu_tag"
233 private const val ARG_GAME_ID = "game_id"
234 private const val FRAGMENT_TAG = "settings"
235
236 fun launch(context: Context, menuTag: String?, gameId: String?) {
237 val settings = Intent(context, SettingsActivity::class.java)
238 settings.putExtra(ARG_MENU_TAG, menuTag)
239 settings.putExtra(ARG_GAME_ID, gameId)
240 context.startActivity(settings)
241 }
242 }
243}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
new file mode 100644
index 000000000..4361d95fb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
@@ -0,0 +1,84 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import android.content.Context
7import android.os.Bundle
8import android.text.TextUtils
9import org.yuzu.yuzu_emu.NativeLibrary
10import org.yuzu.yuzu_emu.features.settings.model.Settings
11import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
12import org.yuzu.yuzu_emu.utils.DirectoryInitialization
13import org.yuzu.yuzu_emu.utils.Log
14import java.io.File
15
16class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
17 val settings: Settings get() = activityView.settings
18
19 private var shouldSave = false
20 private lateinit var menuTag: String
21 private lateinit var gameId: String
22
23 fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
24 this.menuTag = menuTag
25 this.gameId = gameId
26 if (savedInstanceState != null) {
27 shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
28 }
29 }
30
31 fun onStart() {
32 prepareDirectoriesIfNeeded()
33 }
34
35 private fun loadSettingsUI() {
36 if (!settings.isLoaded) {
37 if (!TextUtils.isEmpty(gameId)) {
38 settings.loadSettings(gameId, activityView)
39 } else {
40 settings.loadSettings(activityView)
41 }
42 }
43 activityView.showSettingsFragment(menuTag, false, gameId)
44 activityView.onSettingsFileLoaded()
45 }
46
47 private fun prepareDirectoriesIfNeeded() {
48 val configFile =
49 File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini")
50 if (!configFile.exists()) {
51 Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini")
52 Log.error("yuzu config file could not be found!")
53 }
54
55 if (!DirectoryInitialization.areDirectoriesReady) {
56 DirectoryInitialization.start(activityView as Context)
57 }
58 loadSettingsUI()
59 }
60
61 fun onStop(finishing: Boolean) {
62 if (finishing && shouldSave) {
63 Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
64 settings.saveSettings(activityView)
65 }
66 NativeLibrary.reloadSettings()
67 }
68
69 fun onSettingChanged() {
70 shouldSave = true
71 }
72
73 fun onSettingsReset() {
74 shouldSave = false
75 }
76
77 fun saveState(outState: Bundle) {
78 outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
79 }
80
81 companion object {
82 private const val KEY_SHOULD_SAVE = "should_save"
83 }
84}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
new file mode 100644
index 000000000..c186fc388
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
@@ -0,0 +1,57 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import org.yuzu.yuzu_emu.features.settings.model.Settings
7
8/**
9 * Abstraction for the Activity that manages SettingsFragments.
10 */
11interface SettingsActivityView {
12 /**
13 * Show a new SettingsFragment.
14 *
15 * @param menuTag Identifier for the settings group that should be displayed.
16 * @param addToStack Whether or not this fragment should replace a previous one.
17 */
18 fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
19
20 /**
21 * Called by a contained Fragment to get access to the Setting HashMap
22 * loaded from disk, so that each Fragment doesn't need to perform its own
23 * read operation.
24 *
25 * @return A HashMap of Settings.
26 */
27 val settings: Settings
28
29 /**
30 * Called when a load operation completes.
31 */
32 fun onSettingsFileLoaded()
33
34 /**
35 * Called when a load operation fails.
36 */
37 fun onSettingsFileNotFound()
38
39 /**
40 * Display a popup text message on screen.
41 *
42 * @param message The contents of the onscreen message.
43 * @param is_long Whether this should be a long Toast or short one.
44 */
45 fun showToastMessage(message: String, is_long: Boolean)
46
47 /**
48 * End the activity.
49 */
50 fun finish()
51
52 /**
53 * Called by a containing Fragment to tell the Activity that a setting was changed;
54 * unless this has been called, the Activity will not save to disk.
55 */
56 fun onSettingChanged()
57}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
new file mode 100644
index 000000000..1eb4899fc
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -0,0 +1,340 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import android.content.Context
7import android.content.DialogInterface
8import android.icu.util.Calendar
9import android.icu.util.TimeZone
10import android.text.format.DateFormat
11import android.view.LayoutInflater
12import android.view.ViewGroup
13import android.widget.TextView
14import androidx.appcompat.app.AlertDialog
15import androidx.appcompat.app.AppCompatActivity
16import androidx.fragment.app.setFragmentResultListener
17import androidx.recyclerview.widget.RecyclerView
18import com.google.android.material.datepicker.MaterialDatePicker
19import com.google.android.material.dialog.MaterialAlertDialogBuilder
20import com.google.android.material.slider.Slider
21import com.google.android.material.timepicker.MaterialTimePicker
22import com.google.android.material.timepicker.TimeFormat
23import org.yuzu.yuzu_emu.R
24import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
25import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
26import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
27import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
28import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
29import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
30import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
31import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
32import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
33import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
34import org.yuzu.yuzu_emu.features.settings.model.view.*
35import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
36
37class SettingsAdapter(
38 private val fragmentView: SettingsFragmentView,
39 private val context: Context
40) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
41 private var settings: ArrayList<SettingsItem>? = null
42 private var clickedItem: SettingsItem? = null
43 private var clickedPosition: Int
44 private var dialog: AlertDialog? = null
45 private var sliderProgress = 0
46 private var textSliderValue: TextView? = null
47
48 private var defaultCancelListener =
49 DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
50
51 init {
52 clickedPosition = -1
53 }
54
55 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
56 val inflater = LayoutInflater.from(parent.context)
57 return when (viewType) {
58 SettingsItem.TYPE_HEADER -> {
59 HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
60 }
61
62 SettingsItem.TYPE_SWITCH -> {
63 SwitchSettingViewHolder(ListItemSettingSwitchBinding.inflate(inflater), this)
64 }
65
66 SettingsItem.TYPE_SINGLE_CHOICE, SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
67 SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
68 }
69
70 SettingsItem.TYPE_SLIDER -> {
71 SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
72 }
73
74 SettingsItem.TYPE_SUBMENU -> {
75 SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this)
76 }
77
78 SettingsItem.TYPE_DATETIME_SETTING -> {
79 DateTimeViewHolder(ListItemSettingBinding.inflate(inflater), this)
80 }
81
82 SettingsItem.TYPE_RUNNABLE -> {
83 RunnableViewHolder(ListItemSettingBinding.inflate(inflater), this)
84 }
85
86 else -> {
87 // TODO: Create an error view since we can't return null now
88 HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
89 }
90 }
91 }
92
93 override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
94 holder.bind(getItem(position))
95 }
96
97 private fun getItem(position: Int): SettingsItem {
98 return settings!![position]
99 }
100
101 override fun getItemCount(): Int {
102 return if (settings != null) {
103 settings!!.size
104 } else {
105 0
106 }
107 }
108
109 override fun getItemViewType(position: Int): Int {
110 return getItem(position).type
111 }
112
113 fun setSettingsList(settings: ArrayList<SettingsItem>?) {
114 this.settings = settings
115 notifyDataSetChanged()
116 }
117
118 fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
119 val setting = item.setChecked(checked)
120 fragmentView.putSetting(setting)
121 fragmentView.onSettingChanged()
122 }
123
124 private fun onSingleChoiceClick(item: SingleChoiceSetting) {
125 clickedItem = item
126 val value = getSelectionForSingleChoiceValue(item)
127 dialog = MaterialAlertDialogBuilder(context)
128 .setTitle(item.nameId)
129 .setSingleChoiceItems(item.choicesId, value, this)
130 .show()
131 }
132
133 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
134 clickedPosition = position
135 onSingleChoiceClick(item)
136 }
137
138 private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
139 clickedItem = item
140 dialog = MaterialAlertDialogBuilder(context)
141 .setTitle(item.nameId)
142 .setSingleChoiceItems(item.choicesId, item.selectValueIndex, this)
143 .show()
144 }
145
146 fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
147 clickedPosition = position
148 onStringSingleChoiceClick(item)
149 }
150
151 fun onDateTimeClick(item: DateTimeSetting, position: Int) {
152 clickedItem = item
153 clickedPosition = position
154 val storedTime = java.lang.Long.decode(item.value) * 1000
155
156 // Helper to extract hour and minute from epoch time
157 val calendar: Calendar = Calendar.getInstance()
158 calendar.timeInMillis = storedTime
159 calendar.timeZone = TimeZone.getTimeZone("UTC")
160
161 var timeFormat: Int = TimeFormat.CLOCK_12H
162 if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) {
163 timeFormat = TimeFormat.CLOCK_24H
164 }
165
166 val datePicker: MaterialDatePicker<Long> = MaterialDatePicker.Builder.datePicker()
167 .setSelection(storedTime)
168 .setTitleText(R.string.select_rtc_date)
169 .build()
170 val timePicker: MaterialTimePicker = MaterialTimePicker.Builder()
171 .setTimeFormat(timeFormat)
172 .setHour(calendar.get(Calendar.HOUR_OF_DAY))
173 .setMinute(calendar.get(Calendar.MINUTE))
174 .setTitleText(R.string.select_rtc_time)
175 .build()
176
177 datePicker.addOnPositiveButtonClickListener {
178 timePicker.show(
179 (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
180 "TimePicker"
181 )
182 }
183 timePicker.addOnPositiveButtonClickListener {
184 var epochTime: Long = datePicker.selection!! / 1000
185 epochTime += timePicker.hour.toLong() * 60 * 60
186 epochTime += timePicker.minute.toLong() * 60
187 val rtcString = epochTime.toString()
188 if (item.value != rtcString) {
189 fragmentView.onSettingChanged()
190 }
191 notifyItemChanged(clickedPosition)
192 val setting = item.setSelectedValue(rtcString)
193 fragmentView.putSetting(setting)
194 clickedItem = null
195 }
196 datePicker.show(
197 (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
198 "DatePicker"
199 )
200 }
201
202 fun onSliderClick(item: SliderSetting, position: Int) {
203 clickedItem = item
204 clickedPosition = position
205 sliderProgress = item.selectedValue
206
207 val inflater = LayoutInflater.from(context)
208 val sliderBinding = DialogSliderBinding.inflate(inflater)
209
210 textSliderValue = sliderBinding.textValue
211 textSliderValue!!.text = sliderProgress.toString()
212 sliderBinding.textUnits.text = item.units
213
214 sliderBinding.slider.apply {
215 valueFrom = item.min.toFloat()
216 valueTo = item.max.toFloat()
217 value = sliderProgress.toFloat()
218 addOnChangeListener { _: Slider, value: Float, _: Boolean ->
219 sliderProgress = value.toInt()
220 textSliderValue!!.text = sliderProgress.toString()
221 }
222 }
223
224 dialog = MaterialAlertDialogBuilder(context)
225 .setTitle(item.nameId)
226 .setView(sliderBinding.root)
227 .setPositiveButton(android.R.string.ok, this)
228 .setNegativeButton(android.R.string.cancel, defaultCancelListener)
229 .setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
230 sliderBinding.slider.value = item.defaultValue!!.toFloat()
231 onClick(dialog, which)
232 }
233 .show()
234 }
235
236 fun onSubmenuClick(item: SubmenuSetting) {
237 fragmentView.loadSubMenu(item.menuKey)
238 }
239
240 override fun onClick(dialog: DialogInterface, which: Int) {
241 when (clickedItem) {
242 is SingleChoiceSetting -> {
243 val scSetting = clickedItem as SingleChoiceSetting
244 val value = getValueForSingleChoiceSelection(scSetting, which)
245 if (scSetting.selectedValue != value) {
246 fragmentView.onSettingChanged()
247 }
248
249 // Get the backing Setting, which may be null (if for example it was missing from the file)
250 val setting = scSetting.setSelectedValue(value)
251 fragmentView.putSetting(setting)
252 closeDialog()
253 }
254
255 is StringSingleChoiceSetting -> {
256 val scSetting = clickedItem as StringSingleChoiceSetting
257 val value = scSetting.getValueAt(which)
258 if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
259 val setting = scSetting.setSelectedValue(value!!)
260 fragmentView.putSetting(setting)
261 closeDialog()
262 }
263
264 is SliderSetting -> {
265 val sliderSetting = clickedItem as SliderSetting
266 if (sliderSetting.selectedValue != sliderProgress) {
267 fragmentView.onSettingChanged()
268 }
269 if (sliderSetting.setting is FloatSetting) {
270 val value = sliderProgress.toFloat()
271 val setting = sliderSetting.setSelectedValue(value)
272 fragmentView.putSetting(setting)
273 } else {
274 val setting = sliderSetting.setSelectedValue(sliderProgress)
275 fragmentView.putSetting(setting)
276 }
277 closeDialog()
278 }
279 }
280 clickedItem = null
281 sliderProgress = -1
282 }
283
284 fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
285 MaterialAlertDialogBuilder(context)
286 .setMessage(R.string.reset_setting_confirmation)
287 .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
288 when (setting) {
289 is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
290 is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
291 is AbstractIntSetting -> setting.int = setting.defaultValue as Int
292 is AbstractStringSetting -> setting.string = setting.defaultValue as String
293 }
294 notifyItemChanged(position)
295 fragmentView.onSettingChanged()
296 }
297 .setNegativeButton(android.R.string.cancel, null)
298 .show()
299
300 return true
301 }
302
303 fun closeDialog() {
304 if (dialog != null) {
305 if (clickedPosition != -1) {
306 notifyItemChanged(clickedPosition)
307 clickedPosition = -1
308 }
309 dialog!!.dismiss()
310 dialog = null
311 }
312 }
313
314 private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
315 val valuesId = item.valuesId
316 return if (valuesId > 0) {
317 val valuesArray = context.resources.getIntArray(valuesId)
318 valuesArray[which]
319 } else {
320 which
321 }
322 }
323
324 private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
325 val value = item.selectedValue
326 val valuesId = item.valuesId
327 if (valuesId > 0) {
328 val valuesArray = context.resources.getIntArray(valuesId)
329 for (index in valuesArray.indices) {
330 val current = valuesArray[index]
331 if (current == value) {
332 return index
333 }
334 }
335 } else {
336 return value
337 }
338 return -1
339 }
340}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
new file mode 100644
index 000000000..867147950
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -0,0 +1,122 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import android.content.Context
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import androidx.core.view.ViewCompat
12import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment
15import androidx.recyclerview.widget.LinearLayoutManager
16import com.google.android.material.divider.MaterialDividerItemDecoration
17import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
18import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
19import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
20
21class SettingsFragment : Fragment(), SettingsFragmentView {
22 override var activityView: SettingsActivityView? = null
23
24 private val fragmentPresenter = SettingsFragmentPresenter(this)
25 private var settingsAdapter: SettingsAdapter? = null
26
27 private var _binding: FragmentSettingsBinding? = null
28 private val binding get() = _binding!!
29
30 override fun onAttach(context: Context) {
31 super.onAttach(context)
32 activityView = requireActivity() as SettingsActivityView
33 }
34
35 override fun onCreate(savedInstanceState: Bundle?) {
36 super.onCreate(savedInstanceState)
37 val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
38 val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
39 fragmentPresenter.onCreate(menuTag!!, gameId!!)
40 }
41
42 override fun onCreateView(
43 inflater: LayoutInflater,
44 container: ViewGroup?,
45 savedInstanceState: Bundle?
46 ): View {
47 _binding = FragmentSettingsBinding.inflate(layoutInflater)
48 return binding.root
49 }
50
51 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
52 settingsAdapter = SettingsAdapter(this, requireActivity())
53 val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
54 dividerDecoration.isLastItemDecorated = false
55 binding.listSettings.apply {
56 adapter = settingsAdapter
57 layoutManager = LinearLayoutManager(activity)
58 addItemDecoration(dividerDecoration)
59 }
60 fragmentPresenter.onViewCreated()
61
62 setInsets()
63 }
64
65 override fun onDetach() {
66 super.onDetach()
67 activityView = null
68 if (settingsAdapter != null) {
69 settingsAdapter!!.closeDialog()
70 }
71 }
72
73 override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
74 settingsAdapter!!.setSettingsList(settingsList)
75 }
76
77 override fun loadSettingsList() {
78 fragmentPresenter.loadSettingsList()
79 }
80
81 override fun loadSubMenu(menuKey: String) {
82 activityView!!.showSettingsFragment(
83 menuKey,
84 true,
85 requireArguments().getString(ARGUMENT_GAME_ID)!!
86 )
87 }
88
89 override fun showToastMessage(message: String?, is_long: Boolean) {
90 activityView!!.showToastMessage(message!!, is_long)
91 }
92
93 override fun putSetting(setting: AbstractSetting) {
94 fragmentPresenter.putSetting(setting)
95 }
96
97 override fun onSettingChanged() {
98 activityView!!.onSettingChanged()
99 }
100
101 private fun setInsets() {
102 ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat ->
103 val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
104 view.updatePadding(bottom = insets.bottom)
105 windowInsets
106 }
107 }
108
109 companion object {
110 private const val ARGUMENT_MENU_TAG = "menu_tag"
111 private const val ARGUMENT_GAME_ID = "game_id"
112
113 fun newInstance(menuTag: String?, gameId: String?): Fragment {
114 val fragment = SettingsFragment()
115 val arguments = Bundle()
116 arguments.putString(ARGUMENT_MENU_TAG, menuTag)
117 arguments.putString(ARGUMENT_GAME_ID, gameId)
118 fragment.arguments = arguments
119 return fragment
120 }
121 }
122}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
new file mode 100644
index 000000000..1ceaa6fb4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -0,0 +1,474 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import android.content.SharedPreferences
7import android.os.Build
8import android.text.TextUtils
9import androidx.preference.PreferenceManager
10import com.google.android.material.dialog.MaterialAlertDialogBuilder
11import org.yuzu.yuzu_emu.R
12import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
14import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
15import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
16import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
17import org.yuzu.yuzu_emu.features.settings.model.IntSetting
18import org.yuzu.yuzu_emu.features.settings.model.Settings
19import org.yuzu.yuzu_emu.features.settings.model.StringSetting
20import org.yuzu.yuzu_emu.features.settings.model.view.*
21import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
22import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
23import org.yuzu.yuzu_emu.utils.ThemeHelper
24
25class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
26 private var menuTag: String? = null
27 private lateinit var gameId: String
28 private var settingsList: ArrayList<SettingsItem>? = null
29
30 private val settingsActivity get() = fragmentView.activityView as SettingsActivity
31 private val settings get() = fragmentView.activityView!!.settings
32
33 private lateinit var preferences: SharedPreferences
34
35 fun onCreate(menuTag: String, gameId: String) {
36 this.gameId = gameId
37 this.menuTag = menuTag
38 }
39
40 fun onViewCreated() {
41 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
42 loadSettingsList()
43 }
44
45 fun putSetting(setting: AbstractSetting) {
46 if (setting.section == null) {
47 return
48 }
49
50 val section = settings.getSection(setting.section!!)!!
51 if (section.getSetting(setting.key!!) == null) {
52 section.putSetting(setting)
53 }
54 }
55
56 fun loadSettingsList() {
57 if (!TextUtils.isEmpty(gameId)) {
58 settingsActivity.setToolbarTitle("Game Settings: $gameId")
59 }
60 val sl = ArrayList<SettingsItem>()
61 if (menuTag == null) {
62 return
63 }
64 when (menuTag) {
65 SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
66 Settings.SECTION_GENERAL -> addGeneralSettings(sl)
67 Settings.SECTION_SYSTEM -> addSystemSettings(sl)
68 Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
69 Settings.SECTION_AUDIO -> addAudioSettings(sl)
70 Settings.SECTION_THEME -> addThemeSettings(sl)
71 Settings.SECTION_DEBUG -> addDebugSettings(sl)
72 else -> {
73 fragmentView.showToastMessage("Unimplemented menu", false)
74 return
75 }
76 }
77 settingsList = sl
78 fragmentView.showSettingsList(settingsList!!)
79 }
80
81 private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
82 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
83 sl.apply {
84 add(
85 SubmenuSetting(
86 R.string.preferences_general,
87 0,
88 Settings.SECTION_GENERAL
89 )
90 )
91 add(
92 SubmenuSetting(
93 R.string.preferences_system,
94 0,
95 Settings.SECTION_SYSTEM
96 )
97 )
98 add(
99 SubmenuSetting(
100 R.string.preferences_graphics,
101 0,
102 Settings.SECTION_RENDERER
103 )
104 )
105 add(
106 SubmenuSetting(
107 R.string.preferences_audio,
108 0,
109 Settings.SECTION_AUDIO
110 )
111 )
112 add(
113 SubmenuSetting(
114 R.string.preferences_debug,
115 0,
116 Settings.SECTION_DEBUG
117 )
118 )
119 add(
120 RunnableSetting(
121 R.string.reset_to_default,
122 0,
123 false
124 ) {
125 ResetSettingsDialogFragment().show(
126 settingsActivity.supportFragmentManager,
127 ResetSettingsDialogFragment.TAG
128 )
129 }
130 )
131 }
132 }
133
134 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
135 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general))
136 sl.apply {
137 add(
138 SwitchSetting(
139 IntSetting.RENDERER_USE_SPEED_LIMIT,
140 R.string.frame_limit_enable,
141 R.string.frame_limit_enable_description,
142 IntSetting.RENDERER_USE_SPEED_LIMIT.key,
143 IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
144 )
145 )
146 add(
147 SliderSetting(
148 IntSetting.RENDERER_SPEED_LIMIT,
149 R.string.frame_limit_slider,
150 R.string.frame_limit_slider_description,
151 1,
152 200,
153 "%",
154 IntSetting.RENDERER_SPEED_LIMIT.key,
155 IntSetting.RENDERER_SPEED_LIMIT.defaultValue
156 )
157 )
158 add(
159 SingleChoiceSetting(
160 IntSetting.CPU_ACCURACY,
161 R.string.cpu_accuracy,
162 0,
163 R.array.cpuAccuracyNames,
164 R.array.cpuAccuracyValues,
165 IntSetting.CPU_ACCURACY.key,
166 IntSetting.CPU_ACCURACY.defaultValue
167 )
168 )
169 }
170 }
171
172 private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
173 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system))
174 sl.apply {
175 add(
176 SwitchSetting(
177 IntSetting.USE_DOCKED_MODE,
178 R.string.use_docked_mode,
179 R.string.use_docked_mode_description,
180 IntSetting.USE_DOCKED_MODE.key,
181 IntSetting.USE_DOCKED_MODE.defaultValue
182 )
183 )
184 add(
185 SingleChoiceSetting(
186 IntSetting.REGION_INDEX,
187 R.string.emulated_region,
188 0,
189 R.array.regionNames,
190 R.array.regionValues,
191 IntSetting.REGION_INDEX.key,
192 IntSetting.REGION_INDEX.defaultValue
193 )
194 )
195 add(
196 SingleChoiceSetting(
197 IntSetting.LANGUAGE_INDEX,
198 R.string.emulated_language,
199 0,
200 R.array.languageNames,
201 R.array.languageValues,
202 IntSetting.LANGUAGE_INDEX.key,
203 IntSetting.LANGUAGE_INDEX.defaultValue
204 )
205 )
206 add(
207 SwitchSetting(
208 BooleanSetting.USE_CUSTOM_RTC,
209 R.string.use_custom_rtc,
210 R.string.use_custom_rtc_description,
211 BooleanSetting.USE_CUSTOM_RTC.key,
212 BooleanSetting.USE_CUSTOM_RTC.defaultValue
213 )
214 )
215 add(
216 DateTimeSetting(
217 StringSetting.CUSTOM_RTC,
218 R.string.set_custom_rtc,
219 0,
220 StringSetting.CUSTOM_RTC.key,
221 StringSetting.CUSTOM_RTC.defaultValue
222 )
223 )
224 }
225 }
226
227 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
228 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
229 sl.apply {
230
231 add(
232 SingleChoiceSetting(
233 IntSetting.RENDERER_ACCURACY,
234 R.string.renderer_accuracy,
235 0,
236 R.array.rendererAccuracyNames,
237 R.array.rendererAccuracyValues,
238 IntSetting.RENDERER_ACCURACY.key,
239 IntSetting.RENDERER_ACCURACY.defaultValue
240 )
241 )
242 add(
243 SingleChoiceSetting(
244 IntSetting.RENDERER_RESOLUTION,
245 R.string.renderer_resolution,
246 0,
247 R.array.rendererResolutionNames,
248 R.array.rendererResolutionValues,
249 IntSetting.RENDERER_RESOLUTION.key,
250 IntSetting.RENDERER_RESOLUTION.defaultValue
251 )
252 )
253 add(
254 SingleChoiceSetting(
255 IntSetting.RENDERER_VSYNC,
256 R.string.renderer_vsync,
257 0,
258 R.array.rendererVSyncNames,
259 R.array.rendererVSyncValues,
260 IntSetting.RENDERER_VSYNC.key,
261 IntSetting.RENDERER_VSYNC.defaultValue
262 )
263 )
264 add(
265 SingleChoiceSetting(
266 IntSetting.RENDERER_SCALING_FILTER,
267 R.string.renderer_scaling_filter,
268 0,
269 R.array.rendererScalingFilterNames,
270 R.array.rendererScalingFilterValues,
271 IntSetting.RENDERER_SCALING_FILTER.key,
272 IntSetting.RENDERER_SCALING_FILTER.defaultValue
273 )
274 )
275 add(
276 SingleChoiceSetting(
277 IntSetting.RENDERER_ANTI_ALIASING,
278 R.string.renderer_anti_aliasing,
279 0,
280 R.array.rendererAntiAliasingNames,
281 R.array.rendererAntiAliasingValues,
282 IntSetting.RENDERER_ANTI_ALIASING.key,
283 IntSetting.RENDERER_ANTI_ALIASING.defaultValue
284 )
285 )
286 add(
287 SingleChoiceSetting(
288 IntSetting.RENDERER_ASPECT_RATIO,
289 R.string.renderer_aspect_ratio,
290 0,
291 R.array.rendererAspectRatioNames,
292 R.array.rendererAspectRatioValues,
293 IntSetting.RENDERER_ASPECT_RATIO.key,
294 IntSetting.RENDERER_ASPECT_RATIO.defaultValue
295 )
296 )
297 add(
298 SwitchSetting(
299 IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
300 R.string.use_disk_shader_cache,
301 R.string.use_disk_shader_cache_description,
302 IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
303 IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
304 )
305 )
306 add(
307 SwitchSetting(
308 IntSetting.RENDERER_FORCE_MAX_CLOCK,
309 R.string.renderer_force_max_clock,
310 R.string.renderer_force_max_clock_description,
311 IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
312 IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
313 )
314 )
315 add(
316 SwitchSetting(
317 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
318 R.string.renderer_asynchronous_shaders,
319 R.string.renderer_asynchronous_shaders_description,
320 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
321 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
322 )
323 )
324 add(
325 SwitchSetting(
326 IntSetting.RENDERER_REACTIVE_FLUSHING,
327 R.string.renderer_reactive_flushing,
328 R.string.renderer_reactive_flushing_description,
329 IntSetting.RENDERER_REACTIVE_FLUSHING.key,
330 IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
331 )
332 )
333 }
334 }
335
336 private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
337 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
338 sl.add(
339 SliderSetting(
340 IntSetting.AUDIO_VOLUME,
341 R.string.audio_volume,
342 R.string.audio_volume_description,
343 0,
344 100,
345 "%",
346 IntSetting.AUDIO_VOLUME.key,
347 IntSetting.AUDIO_VOLUME.defaultValue
348 )
349 )
350 }
351
352 private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
353 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
354 sl.apply {
355 val theme: AbstractIntSetting = object : AbstractIntSetting {
356 override var int: Int
357 get() = preferences.getInt(Settings.PREF_THEME, 0)
358 set(value) {
359 preferences.edit()
360 .putInt(Settings.PREF_THEME, value)
361 .apply()
362 settingsActivity.recreate()
363 }
364 override val key: String? = null
365 override val section: String? = null
366 override val isRuntimeEditable: Boolean = false
367 override val valueAsString: String
368 get() = preferences.getInt(Settings.PREF_THEME, 0).toString()
369 override val defaultValue: Any = 0
370 }
371
372 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
373 add(
374 SingleChoiceSetting(
375 theme,
376 R.string.change_app_theme,
377 0,
378 R.array.themeEntriesA12,
379 R.array.themeValuesA12
380 )
381 )
382 } else {
383 add(
384 SingleChoiceSetting(
385 theme,
386 R.string.change_app_theme,
387 0,
388 R.array.themeEntries,
389 R.array.themeValues
390 )
391 )
392 }
393
394 val themeMode: AbstractIntSetting = object : AbstractIntSetting {
395 override var int: Int
396 get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
397 set(value) {
398 preferences.edit()
399 .putInt(Settings.PREF_THEME_MODE, value)
400 .apply()
401 ThemeHelper.setThemeMode(settingsActivity)
402 }
403 override val key: String? = null
404 override val section: String? = null
405 override val isRuntimeEditable: Boolean = false
406 override val valueAsString: String
407 get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString()
408 override val defaultValue: Any = -1
409 }
410
411 add(
412 SingleChoiceSetting(
413 themeMode,
414 R.string.change_theme_mode,
415 0,
416 R.array.themeModeEntries,
417 R.array.themeModeValues
418 )
419 )
420
421 val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
422 override var boolean: Boolean
423 get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
424 set(value) {
425 preferences.edit()
426 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
427 .apply()
428 settingsActivity.recreate()
429 }
430 override val key: String? = null
431 override val section: String? = null
432 override val isRuntimeEditable: Boolean = false
433 override val valueAsString: String
434 get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
435 .toString()
436 override val defaultValue: Any = false
437 }
438
439 add(
440 SwitchSetting(
441 blackBackgrounds,
442 R.string.use_black_backgrounds,
443 R.string.use_black_backgrounds_description
444 )
445 )
446 }
447 }
448
449 private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
450 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
451 sl.apply {
452 add(
453 SingleChoiceSetting(
454 IntSetting.RENDERER_BACKEND,
455 R.string.renderer_api,
456 0,
457 R.array.rendererApiNames,
458 R.array.rendererApiValues,
459 IntSetting.RENDERER_BACKEND.key,
460 IntSetting.RENDERER_BACKEND.defaultValue
461 )
462 )
463 add(
464 SwitchSetting(
465 IntSetting.RENDERER_DEBUG,
466 R.string.renderer_debug,
467 R.string.renderer_debug_description,
468 IntSetting.RENDERER_DEBUG.key,
469 IntSetting.RENDERER_DEBUG.defaultValue
470 )
471 )
472 }
473 }
474}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
new file mode 100644
index 000000000..1ebe35eaa
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
@@ -0,0 +1,58 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
8
9/**
10 * Abstraction for a screen showing a list of settings. Instances of
11 * this type of view will each display a layer of the setting hierarchy.
12 */
13interface SettingsFragmentView {
14 /**
15 * Pass an ArrayList to the View so that it can be displayed on screen.
16 *
17 * @param settingsList The result of converting the HashMap to an ArrayList
18 */
19 fun showSettingsList(settingsList: ArrayList<SettingsItem>)
20
21 /**
22 * Instructs the Fragment to load the settings screen.
23 */
24 fun loadSettingsList()
25
26 /**
27 * @return The Fragment's containing activity.
28 */
29 val activityView: SettingsActivityView?
30
31 /**
32 * Tell the Fragment to tell the containing Activity to show a new
33 * Fragment containing a submenu of settings.
34 *
35 * @param menuKey Identifier for the settings group that should be shown.
36 */
37 fun loadSubMenu(menuKey: String)
38
39 /**
40 * Tell the Fragment to tell the containing activity to display a toast message.
41 *
42 * @param message Text to be shown in the Toast
43 * @param is_long Whether this should be a long Toast or short one.
44 */
45 fun showToastMessage(message: String?, is_long: Boolean)
46
47 /**
48 * Have the fragment add a setting to the HashMap.
49 *
50 * @param setting The (possibly previously missing) new setting.
51 */
52 fun putSetting(setting: AbstractSetting)
53
54 /**
55 * Have the fragment tell the containing Activity that a setting was modified.
56 */
57 fun onSettingChanged()
58}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
new file mode 100644
index 000000000..04c045e77
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5
6import android.view.View
7import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
9import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
10import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
11import java.time.Instant
12import java.time.ZoneId
13import java.time.ZonedDateTime
14import java.time.format.DateTimeFormatter
15import java.time.format.FormatStyle
16
17class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
18 SettingViewHolder(binding.root, adapter) {
19 private lateinit var setting: DateTimeSetting
20
21 override fun bind(item: SettingsItem) {
22 setting = item as DateTimeSetting
23 binding.textSettingName.setText(item.nameId)
24 if (item.descriptionId != 0) {
25 binding.textSettingDescription.setText(item.descriptionId)
26 binding.textSettingDescription.visibility = View.VISIBLE
27 } else {
28 val epochTime = setting.value.toLong()
29 val instant = Instant.ofEpochMilli(epochTime * 1000)
30 val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
31 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
32 binding.textSettingDescription.text = dateFormatter.format(zonedTime)
33 }
34 }
35
36 override fun onClick(clicked: View) {
37 if (setting.isEditable) {
38 adapter.onDateTimeClick(setting, bindingAdapterPosition)
39 }
40 }
41
42 override fun onLongClick(clicked: View): Boolean {
43 if (setting.isEditable) {
44 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
45 }
46 return false
47 }
48}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt
new file mode 100644
index 000000000..f5bcf705c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt
@@ -0,0 +1,30 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5
6import android.view.View
7import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
10
11class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: SettingsAdapter) :
12 SettingViewHolder(binding.root, adapter) {
13
14 init {
15 itemView.setOnClickListener(null)
16 }
17
18 override fun bind(item: SettingsItem) {
19 binding.textHeaderName.setText(item.nameId)
20 }
21
22 override fun onClick(clicked: View) {
23 // no-op
24 }
25
26 override fun onLongClick(clicked: View): Boolean {
27 // no-op
28 return true
29 }
30}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
new file mode 100644
index 000000000..5dad5945f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5
6import android.view.View
7import org.yuzu.yuzu_emu.NativeLibrary
8import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
9import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting
10import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
12
13class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
14 SettingViewHolder(binding.root, adapter) {
15 private lateinit var setting: RunnableSetting
16
17 override fun bind(item: SettingsItem) {
18 setting = item as RunnableSetting
19 binding.textSettingName.setText(item.nameId)
20 if (item.descriptionId != 0) {
21 binding.textSettingDescription.setText(item.descriptionId)
22 binding.textSettingDescription.visibility = View.VISIBLE
23 } else {
24 binding.textSettingDescription.visibility = View.GONE
25 }
26 }
27
28 override fun onClick(clicked: View) {
29 if (!setting.isRuntimeRunnable && !NativeLibrary.isRunning()) {
30 setting.runnable.invoke()
31 }
32 }
33
34 override fun onLongClick(clicked: View): Boolean {
35 // no-op
36 return true
37 }
38}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
new file mode 100644
index 000000000..f56460893
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5
6import android.view.View
7import androidx.recyclerview.widget.RecyclerView
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
10
11abstract class SettingViewHolder(itemView: View, protected val adapter: SettingsAdapter) :
12 RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
13
14 init {
15 itemView.setOnClickListener(this)
16 itemView.setOnLongClickListener(this)
17 }
18
19 /**
20 * Called by the adapter to set this ViewHolder's child views to display the list item
21 * it must now represent.
22 *
23 * @param item The list item that should be represented by this ViewHolder.
24 */
25 abstract fun bind(item: SettingsItem)
26
27 /**
28 * Called when this ViewHolder's view is clicked on. Implementations should usually pass
29 * this event up to the adapter.
30 *
31 * @param clicked The view that was clicked on.
32 */
33 abstract override fun onClick(clicked: View)
34
35 abstract override fun onLongClick(clicked: View): Boolean
36}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
new file mode 100644
index 000000000..de764a27f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -0,0 +1,60 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5
6import android.view.View
7import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
10import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
12
13class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
14 SettingViewHolder(binding.root, adapter) {
15 private lateinit var setting: SettingsItem
16
17 override fun bind(item: SettingsItem) {
18 setting = item
19 binding.textSettingName.setText(item.nameId)
20 binding.textSettingDescription.visibility = View.VISIBLE
21 if (item.descriptionId != 0) {
22 binding.textSettingDescription.setText(item.descriptionId)
23 } else if (item is SingleChoiceSetting) {
24 val resMgr = binding.textSettingDescription.context.resources
25 val values = resMgr.getIntArray(item.valuesId)
26 for (i in values.indices) {
27 if (values[i] == item.selectedValue) {
28 binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i]
29 }
30 }
31 } else {
32 binding.textSettingDescription.visibility = View.GONE
33 }
34 }
35
36 override fun onClick(clicked: View) {
37 if (!setting.isEditable) {
38 return
39 }
40
41 if (setting is SingleChoiceSetting) {
42 adapter.onSingleChoiceClick(
43 (setting as SingleChoiceSetting),
44 bindingAdapterPosition
45 )
46 } else if (setting is StringSingleChoiceSetting) {
47 adapter.onStringSingleChoiceClick(
48 (setting as StringSingleChoiceSetting),
49 bindingAdapterPosition
50 )
51 }
52 }
53
54 override fun onLongClick(clicked: View): Boolean {
55 if (setting.isEditable) {
56 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
57 }
58 return false
59 }
60}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
new file mode 100644
index 000000000..cc3f39aa5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
@@ -0,0 +1,39 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5
6import android.view.View
7import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
10import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
11
12class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
13 SettingViewHolder(binding.root, adapter) {
14 private lateinit var setting: SliderSetting
15
16 override fun bind(item: SettingsItem) {
17 setting = item as SliderSetting
18 binding.textSettingName.setText(item.nameId)
19 if (item.descriptionId != 0) {
20 binding.textSettingDescription.setText(item.descriptionId)
21 binding.textSettingDescription.visibility = View.VISIBLE
22 } else {
23 binding.textSettingDescription.visibility = View.GONE
24 }
25 }
26
27 override fun onClick(clicked: View) {
28 if (setting.isEditable) {
29 adapter.onSliderClick(setting, bindingAdapterPosition)
30 }
31 }
32
33 override fun onLongClick(clicked: View): Boolean {
34 if (setting.isEditable) {
35 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
36 }
37 return false
38 }
39}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
new file mode 100644
index 000000000..c545b4174
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5
6import android.view.View
7import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting
10import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
11
12class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
13 SettingViewHolder(binding.root, adapter) {
14 private lateinit var item: SubmenuSetting
15
16 override fun bind(item: SettingsItem) {
17 this.item = item as SubmenuSetting
18 binding.textSettingName.setText(item.nameId)
19 if (item.descriptionId != 0) {
20 binding.textSettingDescription.setText(item.descriptionId)
21 binding.textSettingDescription.visibility = View.VISIBLE
22 } else {
23 binding.textSettingDescription.visibility = View.GONE
24 }
25 }
26
27 override fun onClick(clicked: View) {
28 adapter.onSubmenuClick(item)
29 }
30
31 override fun onLongClick(clicked: View): Boolean {
32 // no-op
33 return true
34 }
35}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
new file mode 100644
index 000000000..b163bd6ca
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5
6import android.view.View
7import android.widget.CompoundButton
8import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
9import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
10import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
12
13class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
14 SettingViewHolder(binding.root, adapter) {
15
16 private lateinit var setting: SwitchSetting
17
18 override fun bind(item: SettingsItem) {
19 setting = item as SwitchSetting
20 binding.textSettingName.setText(item.nameId)
21 if (item.descriptionId != 0) {
22 binding.textSettingDescription.setText(item.descriptionId)
23 binding.textSettingDescription.visibility = View.VISIBLE
24 } else {
25 binding.textSettingDescription.text = ""
26 binding.textSettingDescription.visibility = View.GONE
27 }
28 binding.switchWidget.isChecked = setting.isChecked
29 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
30 adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
31 }
32
33 binding.switchWidget.isEnabled = setting.isEditable
34 }
35
36 override fun onClick(clicked: View) {
37 if (setting.isEditable) {
38 binding.switchWidget.toggle()
39 }
40 }
41
42 override fun onLongClick(clicked: View): Boolean {
43 if (setting.isEditable) {
44 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
45 }
46 return false
47 }
48}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
new file mode 100644
index 000000000..e29bca11d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
@@ -0,0 +1,241 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.utils
5
6import org.ini4j.Wini
7import org.yuzu.yuzu_emu.NativeLibrary
8import org.yuzu.yuzu_emu.R
9import org.yuzu.yuzu_emu.YuzuApplication
10import org.yuzu.yuzu_emu.features.settings.model.*
11import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
12import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
13import org.yuzu.yuzu_emu.utils.BiMap
14import org.yuzu.yuzu_emu.utils.DirectoryInitialization
15import org.yuzu.yuzu_emu.utils.Log
16import java.io.*
17import java.util.*
18
19/**
20 * Contains static methods for interacting with .ini files in which settings are stored.
21 */
22object SettingsFile {
23 const val FILE_NAME_CONFIG = "config"
24
25 private var sectionsMap = BiMap<String?, String?>()
26
27 /**
28 * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
29 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
30 * failed.
31 *
32 * @param ini The ini file to load the settings from
33 * @param isCustomGame
34 * @param view The current view.
35 * @return An Observable that emits a HashMap of the file's contents, then completes.
36 */
37 private fun readFile(
38 ini: File?,
39 isCustomGame: Boolean,
40 view: SettingsActivityView? = null
41 ): HashMap<String, SettingSection?> {
42 val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
43 var reader: BufferedReader? = null
44 try {
45 reader = BufferedReader(FileReader(ini))
46 var current: SettingSection? = null
47 var line: String?
48 while (reader.readLine().also { line = it } != null) {
49 if (line!!.startsWith("[") && line!!.endsWith("]")) {
50 current = sectionFromLine(line!!, isCustomGame)
51 sections[current.name] = current
52 } else if (current != null) {
53 val setting = settingFromLine(line!!)
54 if (setting != null) {
55 current.putSetting(setting)
56 }
57 }
58 }
59 } catch (e: FileNotFoundException) {
60 Log.error("[SettingsFile] File not found: " + e.message)
61 view?.onSettingsFileNotFound()
62 } catch (e: IOException) {
63 Log.error("[SettingsFile] Error reading from: " + e.message)
64 view?.onSettingsFileNotFound()
65 } finally {
66 if (reader != null) {
67 try {
68 reader.close()
69 } catch (e: IOException) {
70 Log.error("[SettingsFile] Error closing: " + e.message)
71 }
72 }
73 }
74 return sections
75 }
76
77 fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
78 return readFile(getSettingsFile(fileName), false, view)
79 }
80
81 fun readFile(fileName: String): HashMap<String, SettingSection?> =
82 readFile(getSettingsFile(fileName), false)
83
84 /**
85 * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
86 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
87 * failed.
88 *
89 * @param gameId the id of the game to load it's settings.
90 * @param view The current view.
91 */
92 fun readCustomGameSettings(
93 gameId: String,
94 view: SettingsActivityView?
95 ): HashMap<String, SettingSection?> {
96 return readFile(getCustomGameSettingsFile(gameId), true, view)
97 }
98
99 /**
100 * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
101 * telling why it failed.
102 *
103 * @param fileName The target filename without a path or extension.
104 * @param sections The HashMap containing the Settings we want to serialize.
105 * @param view The current view.
106 */
107 fun saveFile(
108 fileName: String,
109 sections: TreeMap<String, SettingSection>,
110 view: SettingsActivityView
111 ) {
112 val ini = getSettingsFile(fileName)
113 try {
114 val writer = Wini(ini)
115 val keySet: Set<String> = sections.keys
116 for (key in keySet) {
117 val section = sections[key]
118 writeSection(writer, section!!)
119 }
120 writer.store()
121 } catch (e: IOException) {
122 Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
123 view.showToastMessage(
124 YuzuApplication.appContext
125 .getString(R.string.error_saving, fileName, e.message),
126 false
127 )
128 }
129 }
130
131 fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
132 val sortedSections: Set<String> = TreeSet(sections.keys)
133 for (sectionKey in sortedSections) {
134 val section = sections[sectionKey]
135 val settings = section!!.settings
136 val sortedKeySet: Set<String> = TreeSet(settings.keys)
137 for (settingKey in sortedKeySet) {
138 val setting = settings[settingKey]
139 NativeLibrary.setUserSetting(
140 gameId, mapSectionNameFromIni(
141 section.name
142 ), setting!!.key, setting.valueAsString
143 )
144 }
145 }
146 }
147
148 private fun mapSectionNameFromIni(generalSectionName: String): String? {
149 return if (sectionsMap.getForward(generalSectionName) != null) {
150 sectionsMap.getForward(generalSectionName)
151 } else generalSectionName
152 }
153
154 private fun mapSectionNameToIni(generalSectionName: String): String {
155 return if (sectionsMap.getBackward(generalSectionName) != null) {
156 sectionsMap.getBackward(generalSectionName).toString()
157 } else generalSectionName
158 }
159
160 fun getSettingsFile(fileName: String): File {
161 return File(
162 DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
163 )
164 }
165
166 private fun getCustomGameSettingsFile(gameId: String): File {
167 return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
168 }
169
170 private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
171 var sectionName: String = line.substring(1, line.length - 1)
172 if (isCustomGame) {
173 sectionName = mapSectionNameToIni(sectionName)
174 }
175 return SettingSection(sectionName)
176 }
177
178 /**
179 * For a line of text, determines what type of data is being represented, and returns
180 * a Setting object containing this data.
181 *
182 * @param line The line of text being parsed.
183 * @return A typed Setting containing the key/value contained in the line.
184 */
185 private fun settingFromLine(line: String): AbstractSetting? {
186 val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
187 if (splitLine.size != 2) {
188 return null
189 }
190 val key = splitLine[0].trim { it <= ' ' }
191 val value = splitLine[1].trim { it <= ' ' }
192 if (value.isEmpty()) {
193 return null
194 }
195
196 val booleanSetting = BooleanSetting.from(key)
197 if (booleanSetting != null) {
198 booleanSetting.boolean = value.toBoolean()
199 return booleanSetting
200 }
201
202 val intSetting = IntSetting.from(key)
203 if (intSetting != null) {
204 intSetting.int = value.toInt()
205 return intSetting
206 }
207
208 val floatSetting = FloatSetting.from(key)
209 if (floatSetting != null) {
210 floatSetting.float = value.toFloat()
211 return floatSetting
212 }
213
214 val stringSetting = StringSetting.from(key)
215 if (stringSetting != null) {
216 stringSetting.string = value
217 return stringSetting
218 }
219
220 return null
221 }
222
223 /**
224 * Writes the contents of a Section HashMap to disk.
225 *
226 * @param parser A Wini pointed at a file on disk.
227 * @param section A section containing settings to be written to the file.
228 */
229 private fun writeSection(parser: Wini, section: SettingSection) {
230 // Write the section header.
231 val header = section.name
232
233 // Write this section's values.
234 val settings = section.settings
235 val keySet: Set<String> = settings.keys
236 for (key in keySet) {
237 val setting = settings[key]
238 parser.put(header, setting!!.key, setting.valueAsString)
239 }
240 }
241}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
new file mode 100644
index 000000000..c92e2755c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
@@ -0,0 +1,125 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.content.ClipData
7import android.content.ClipboardManager
8import android.content.Context
9import android.content.Intent
10import android.net.Uri
11import android.os.Build
12import android.os.Bundle
13import android.view.LayoutInflater
14import android.view.View
15import android.view.ViewGroup
16import android.view.ViewGroup.MarginLayoutParams
17import android.widget.Toast
18import androidx.core.view.ViewCompat
19import androidx.core.view.WindowInsetsCompat
20import androidx.core.view.updatePadding
21import androidx.fragment.app.Fragment
22import androidx.fragment.app.activityViewModels
23import androidx.navigation.findNavController
24import com.google.android.material.transition.MaterialSharedAxis
25import org.yuzu.yuzu_emu.BuildConfig
26import org.yuzu.yuzu_emu.R
27import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding
28import org.yuzu.yuzu_emu.model.HomeViewModel
29
30class AboutFragment : Fragment() {
31 private var _binding: FragmentAboutBinding? = null
32 private val binding get() = _binding!!
33
34 private val homeViewModel: HomeViewModel by activityViewModels()
35
36 override fun onCreate(savedInstanceState: Bundle?) {
37 super.onCreate(savedInstanceState)
38 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
39 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
40 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
41 }
42
43 override fun onCreateView(
44 inflater: LayoutInflater,
45 container: ViewGroup?,
46 savedInstanceState: Bundle?
47 ): View {
48 _binding = FragmentAboutBinding.inflate(layoutInflater)
49 return binding.root
50 }
51
52 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
53 homeViewModel.setNavigationVisibility(visible = false, animated = true)
54 homeViewModel.setStatusBarShadeVisibility(visible = false)
55
56 binding.toolbarAbout.setNavigationOnClickListener {
57 binding.root.findNavController().popBackStack()
58 }
59
60 binding.imageLogo.setOnLongClickListener {
61 Toast.makeText(
62 requireContext(),
63 R.string.gaia_is_not_real,
64 Toast.LENGTH_SHORT
65 ).show()
66 true
67 }
68
69 binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) }
70 binding.buttonLicenses.setOnClickListener {
71 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
72 binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
73 }
74
75 binding.textBuildHash.text = BuildConfig.GIT_HASH
76 binding.buttonBuildHash.setOnClickListener {
77 val clipBoard =
78 requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
79 val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH)
80 clipBoard.setPrimaryClip(clip)
81
82 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
83 Toast.makeText(
84 requireContext(),
85 R.string.copied_to_clipboard,
86 Toast.LENGTH_SHORT
87 ).show()
88 }
89 }
90
91 binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.support_link)) }
92 binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) }
93 binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) }
94
95 setInsets()
96 }
97
98 private fun openLink(link: String) {
99 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
100 startActivity(intent)
101 }
102
103 private fun setInsets() =
104 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
105 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
106 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
107
108 val leftInsets = barInsets.left + cutoutInsets.left
109 val rightInsets = barInsets.right + cutoutInsets.right
110
111 val mlpAppBar = binding.appbarAbout.layoutParams as MarginLayoutParams
112 mlpAppBar.leftMargin = leftInsets
113 mlpAppBar.rightMargin = rightInsets
114 binding.appbarAbout.layoutParams = mlpAppBar
115
116 val mlpScrollAbout = binding.scrollAbout.layoutParams as MarginLayoutParams
117 mlpScrollAbout.leftMargin = leftInsets
118 mlpScrollAbout.rightMargin = rightInsets
119 binding.scrollAbout.layoutParams = mlpScrollAbout
120
121 binding.contentAbout.updatePadding(bottom = barInsets.bottom)
122
123 windowInsets
124 }
125}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt
new file mode 100644
index 000000000..d8bbc1ce4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt
@@ -0,0 +1,83 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.content.Intent
7import android.net.Uri
8import android.os.Bundle
9import android.view.LayoutInflater
10import android.view.View
11import android.view.ViewGroup
12import androidx.core.view.ViewCompat
13import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding
15import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels
17import androidx.navigation.fragment.findNavController
18import com.google.android.material.transition.MaterialSharedAxis
19import org.yuzu.yuzu_emu.R
20import org.yuzu.yuzu_emu.databinding.FragmentEarlyAccessBinding
21import org.yuzu.yuzu_emu.model.HomeViewModel
22
23class EarlyAccessFragment : Fragment() {
24 private var _binding: FragmentEarlyAccessBinding? = null
25 private val binding get() = _binding!!
26
27 private val homeViewModel: HomeViewModel by activityViewModels()
28
29 override fun onCreate(savedInstanceState: Bundle?) {
30 super.onCreate(savedInstanceState)
31 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
32 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
33 }
34
35 override fun onCreateView(
36 inflater: LayoutInflater,
37 container: ViewGroup?,
38 savedInstanceState: Bundle?
39 ): View {
40 _binding = FragmentEarlyAccessBinding.inflate(layoutInflater)
41 return binding.root
42 }
43
44 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
45 homeViewModel.setNavigationVisibility(visible = false, animated = true)
46 homeViewModel.setStatusBarShadeVisibility(visible = false)
47
48 binding.toolbarAbout.setNavigationOnClickListener {
49 parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack()
50 }
51
52 binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) }
53
54 setInsets()
55 }
56
57 private fun openLink(link: String) {
58 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
59 startActivity(intent)
60 }
61
62 private fun setInsets() =
63 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
64 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
65 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
66
67 val leftInsets = barInsets.left + cutoutInsets.left
68 val rightInsets = barInsets.right + cutoutInsets.right
69
70 val mlpAppBar = binding.appbarEa.layoutParams as ViewGroup.MarginLayoutParams
71 mlpAppBar.leftMargin = leftInsets
72 mlpAppBar.rightMargin = rightInsets
73 binding.appbarEa.layoutParams = mlpAppBar
74
75 binding.scrollEa.updatePadding(
76 left = leftInsets,
77 right = rightInsets,
78 bottom = barInsets.bottom
79 )
80
81 windowInsets
82 }
83}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
new file mode 100644
index 000000000..9523381cd
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -0,0 +1,613 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.annotation.SuppressLint
7import android.app.AlertDialog
8import android.content.Context
9import android.content.DialogInterface
10import android.content.SharedPreferences
11import android.content.pm.ActivityInfo
12import android.content.res.Resources
13import android.graphics.Color
14import android.os.Bundle
15import android.os.Handler
16import android.os.Looper
17import android.util.Rational
18import android.util.TypedValue
19import android.view.*
20import android.widget.TextView
21import androidx.activity.OnBackPressedCallback
22import androidx.appcompat.widget.PopupMenu
23import androidx.core.content.res.ResourcesCompat
24import androidx.core.graphics.Insets
25import androidx.core.view.ViewCompat
26import androidx.core.view.WindowInsetsCompat
27import androidx.core.view.updatePadding
28import androidx.fragment.app.Fragment
29import androidx.preference.PreferenceManager
30import androidx.window.layout.FoldingFeature
31import androidx.window.layout.WindowLayoutInfo
32import com.google.android.material.dialog.MaterialAlertDialogBuilder
33import com.google.android.material.slider.Slider
34import org.yuzu.yuzu_emu.NativeLibrary
35import org.yuzu.yuzu_emu.R
36import org.yuzu.yuzu_emu.YuzuApplication
37import org.yuzu.yuzu_emu.activities.EmulationActivity
38import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
39import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
40import org.yuzu.yuzu_emu.features.settings.model.IntSetting
41import org.yuzu.yuzu_emu.features.settings.model.Settings
42import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
43import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
44import org.yuzu.yuzu_emu.model.Game
45import org.yuzu.yuzu_emu.utils.*
46import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
47
48class EmulationFragment : Fragment(), SurfaceHolder.Callback {
49 private lateinit var preferences: SharedPreferences
50 private lateinit var emulationState: EmulationState
51 private var emulationActivity: EmulationActivity? = null
52 private var perfStatsUpdater: (() -> Unit)? = null
53
54 private var _binding: FragmentEmulationBinding? = null
55 private val binding get() = _binding!!
56
57 private lateinit var game: Game
58
59 override fun onAttach(context: Context) {
60 super.onAttach(context)
61 if (context is EmulationActivity) {
62 emulationActivity = context
63 NativeLibrary.setEmulationActivity(context)
64 } else {
65 throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
66 }
67 }
68
69 /**
70 * Initialize anything that doesn't depend on the layout / views in here.
71 */
72 override fun onCreate(savedInstanceState: Bundle?) {
73 super.onCreate(savedInstanceState)
74
75 // So this fragment doesn't restart on configuration changes; i.e. rotation.
76 retainInstance = true
77 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
78 game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!!
79 emulationState = EmulationState(game.path)
80 }
81
82 /**
83 * Initialize the UI and start emulation in here.
84 */
85 override fun onCreateView(
86 inflater: LayoutInflater,
87 container: ViewGroup?,
88 savedInstanceState: Bundle?
89 ): View {
90 _binding = FragmentEmulationBinding.inflate(layoutInflater)
91 return binding.root
92 }
93
94 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
95 binding.surfaceEmulation.holder.addCallback(this)
96 binding.showFpsText.setTextColor(Color.YELLOW)
97 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
98
99 // Setup overlay.
100 updateShowFpsOverlay()
101
102 binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
103 game.title
104 binding.inGameMenu.setNavigationItemSelectedListener {
105 when (it.itemId) {
106 R.id.menu_pause_emulation -> {
107 if (emulationState.isPaused) {
108 emulationState.run(false)
109 it.title = resources.getString(R.string.emulation_pause)
110 it.icon = ResourcesCompat.getDrawable(
111 resources,
112 R.drawable.ic_pause,
113 requireContext().theme
114 )
115 } else {
116 emulationState.pause()
117 it.title = resources.getString(R.string.emulation_unpause)
118 it.icon = ResourcesCompat.getDrawable(
119 resources,
120 R.drawable.ic_play,
121 requireContext().theme
122 )
123 }
124 true
125 }
126
127 R.id.menu_settings -> {
128 SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
129 true
130 }
131
132 R.id.menu_overlay_controls -> {
133 showOverlayOptions()
134 true
135 }
136
137 R.id.menu_exit -> {
138 emulationState.stop()
139 requireActivity().finish()
140 true
141 }
142
143 else -> true
144 }
145 }
146
147 setInsets()
148
149 requireActivity().onBackPressedDispatcher.addCallback(
150 requireActivity(),
151 object : OnBackPressedCallback(true) {
152 override fun handleOnBackPressed() {
153 if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open()
154 }
155 })
156 }
157
158 override fun onResume() {
159 super.onResume()
160 if (!DirectoryInitialization.areDirectoriesReady) {
161 DirectoryInitialization.start(requireContext())
162 }
163
164 binding.surfaceEmulation.setAspectRatio(
165 when (IntSetting.RENDERER_ASPECT_RATIO.int) {
166 0 -> Rational(16, 9)
167 1 -> Rational(4, 3)
168 2 -> Rational(21, 9)
169 3 -> Rational(16, 10)
170 4 -> null // Stretch
171 else -> Rational(16, 9)
172 }
173 )
174
175 emulationState.run(emulationActivity!!.isActivityRecreated)
176 }
177
178 override fun onPause() {
179 if (emulationState.isRunning) {
180 emulationState.pause()
181 }
182 super.onPause()
183 }
184
185 override fun onDestroyView() {
186 super.onDestroyView()
187 _binding = null
188 }
189
190 override fun onDetach() {
191 NativeLibrary.clearEmulationActivity()
192 super.onDetach()
193 }
194
195 private fun refreshInputOverlay() {
196 binding.surfaceInputOverlay.refreshControls()
197 }
198
199 private fun resetInputOverlay() {
200 preferences.edit()
201 .remove(Settings.PREF_CONTROL_SCALE)
202 .remove(Settings.PREF_CONTROL_OPACITY)
203 .apply()
204 binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() }
205 }
206
207 private fun updateShowFpsOverlay() {
208 if (EmulationMenuSettings.showFps) {
209 val SYSTEM_FPS = 0
210 val FPS = 1
211 val FRAMETIME = 2
212 val SPEED = 3
213 perfStatsUpdater = {
214 val perfStats = NativeLibrary.getPerfStats()
215 if (perfStats[FPS] > 0 && _binding != null) {
216 binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
217 }
218
219 if (!emulationState.isStopped) {
220 perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
221 }
222 }
223 perfStatsUpdateHandler.post(perfStatsUpdater!!)
224 binding.showFpsText.text = resources.getString(R.string.emulation_game_loading)
225 binding.showFpsText.visibility = View.VISIBLE
226 } else {
227 if (perfStatsUpdater != null) {
228 perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
229 }
230 binding.showFpsText.visibility = View.GONE
231 }
232 }
233
234 private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt()
235
236 fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) {
237 val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
238 if (it.isSeparating) {
239 emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
240 if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
241 binding.surfaceEmulation.layoutParams.height = it.bounds.top
242 binding.inGameMenu.layoutParams.height = it.bounds.bottom
243 binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx
244 binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx)
245 }
246 }
247 it.isSeparating
248 } ?: false
249 if (!isFolding) {
250 binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
251 binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
252 binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
253 binding.overlayContainer.updatePadding(0, 0, 0, 0)
254 emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
255 }
256 binding.surfaceInputOverlay.requestLayout()
257 binding.inGameMenu.requestLayout()
258 binding.overlayContainer.requestLayout()
259 }
260
261 override fun surfaceCreated(holder: SurfaceHolder) {
262 // We purposely don't do anything here.
263 // All work is done in surfaceChanged, which we are guaranteed to get even for surface creation.
264 }
265
266 override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
267 Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height)
268 emulationState.newSurface(holder.surface)
269 }
270
271 override fun surfaceDestroyed(holder: SurfaceHolder) {
272 emulationState.clearSurface()
273 }
274
275 private fun showOverlayOptions() {
276 val anchor = binding.inGameMenu.findViewById<View>(R.id.menu_overlay_controls)
277 val popup = PopupMenu(requireContext(), anchor)
278
279 popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
280
281 popup.menu.apply {
282 findItem(R.id.menu_toggle_fps).isChecked = EmulationMenuSettings.showFps
283 findItem(R.id.menu_rel_stick_center).isChecked = EmulationMenuSettings.joystickRelCenter
284 findItem(R.id.menu_dpad_slide).isChecked = EmulationMenuSettings.dpadSlide
285 findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
286 findItem(R.id.menu_haptics).isChecked = EmulationMenuSettings.hapticFeedback
287 }
288
289 popup.setOnMenuItemClickListener {
290 when (it.itemId) {
291 R.id.menu_toggle_fps -> {
292 it.isChecked = !it.isChecked
293 EmulationMenuSettings.showFps = it.isChecked
294 updateShowFpsOverlay()
295 true
296 }
297
298 R.id.menu_edit_overlay -> {
299 binding.drawerLayout.close()
300 binding.surfaceInputOverlay.requestFocus()
301 startConfiguringControls()
302 true
303 }
304
305 R.id.menu_adjust_overlay -> {
306 adjustOverlay()
307 true
308 }
309
310 R.id.menu_toggle_controls -> {
311 val preferences =
312 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
313 val optionsArray = BooleanArray(15)
314 for (i in 0..14) {
315 optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 13)
316 }
317
318 val dialog = MaterialAlertDialogBuilder(requireContext())
319 .setTitle(R.string.emulation_toggle_controls)
320 .setMultiChoiceItems(
321 R.array.gamepadButtons,
322 optionsArray
323 ) { _, indexSelected, isChecked ->
324 preferences.edit()
325 .putBoolean("buttonToggle$indexSelected", isChecked)
326 .apply()
327 }
328 .setPositiveButton(android.R.string.ok) { _, _ ->
329 refreshInputOverlay()
330 }
331 .setNegativeButton(android.R.string.cancel, null)
332 .setNeutralButton(R.string.emulation_toggle_all) { _, _ -> }
333 .show()
334
335 // Override normal behaviour so the dialog doesn't close
336 dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
337 .setOnClickListener {
338 val isChecked = !optionsArray[0]
339 for (i in 0..14) {
340 optionsArray[i] = isChecked
341 dialog.listView.setItemChecked(i, isChecked)
342 preferences.edit()
343 .putBoolean("buttonToggle$i", isChecked)
344 .apply()
345 }
346 }
347 true
348 }
349
350 R.id.menu_show_overlay -> {
351 it.isChecked = !it.isChecked
352 EmulationMenuSettings.showOverlay = it.isChecked
353 refreshInputOverlay()
354 true
355 }
356
357 R.id.menu_rel_stick_center -> {
358 it.isChecked = !it.isChecked
359 EmulationMenuSettings.joystickRelCenter = it.isChecked
360 true
361 }
362
363 R.id.menu_dpad_slide -> {
364 it.isChecked = !it.isChecked
365 EmulationMenuSettings.dpadSlide = it.isChecked
366 true
367 }
368
369 R.id.menu_haptics -> {
370 it.isChecked = !it.isChecked
371 EmulationMenuSettings.hapticFeedback = it.isChecked
372 true
373 }
374
375 R.id.menu_reset_overlay -> {
376 binding.drawerLayout.close()
377 resetInputOverlay()
378 true
379 }
380
381 else -> true
382 }
383 }
384
385 popup.show()
386 }
387
388 private fun startConfiguringControls() {
389 binding.doneControlConfig.visibility = View.VISIBLE
390 binding.surfaceInputOverlay.setIsInEditMode(true)
391 }
392
393 private fun stopConfiguringControls() {
394 binding.doneControlConfig.visibility = View.GONE
395 binding.surfaceInputOverlay.setIsInEditMode(false)
396 }
397
398 @SuppressLint("SetTextI18n")
399 private fun adjustOverlay() {
400 val adjustBinding = DialogOverlayAdjustBinding.inflate(layoutInflater)
401 adjustBinding.apply {
402 inputScaleSlider.apply {
403 valueTo = 150F
404 value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
405 addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
406 inputScaleValue.text = "${value.toInt()}%"
407 setControlScale(value.toInt())
408 })
409 }
410 inputOpacitySlider.apply {
411 valueTo = 100F
412 value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
413 addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
414 inputOpacityValue.text = "${value.toInt()}%"
415 setControlOpacity(value.toInt())
416 })
417 }
418 inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
419 inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
420 }
421
422 MaterialAlertDialogBuilder(requireContext())
423 .setTitle(R.string.emulation_control_adjust)
424 .setView(adjustBinding.root)
425 .setPositiveButton(android.R.string.ok, null)
426 .setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
427 setControlScale(50)
428 setControlOpacity(100)
429 }
430 .show()
431 }
432
433 private fun setControlScale(scale: Int) {
434 preferences.edit()
435 .putInt(Settings.PREF_CONTROL_SCALE, scale)
436 .apply()
437 refreshInputOverlay()
438 }
439
440 private fun setControlOpacity(opacity: Int) {
441 preferences.edit()
442 .putInt(Settings.PREF_CONTROL_OPACITY, opacity)
443 .apply()
444 refreshInputOverlay()
445 }
446
447 private fun setInsets() {
448 ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat ->
449 val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
450 var left = 0
451 var right = 0
452 if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
453 left = cutInsets.left
454 } else {
455 right = cutInsets.right
456 }
457
458 v.setPadding(left, cutInsets.top, right, 0)
459
460 // Ensure FPS text doesn't get cut off by rounded display corners
461 val sidePadding = resources.getDimensionPixelSize(R.dimen.spacing_xtralarge)
462 if (cutInsets.left == 0) {
463 binding.showFpsText.setPadding(
464 sidePadding,
465 cutInsets.top,
466 cutInsets.right,
467 cutInsets.bottom
468 )
469 } else {
470 binding.showFpsText.setPadding(
471 cutInsets.left,
472 cutInsets.top,
473 cutInsets.right,
474 cutInsets.bottom
475 )
476 }
477 windowInsets
478 }
479 }
480
481 private class EmulationState(private val gamePath: String) {
482 private var state: State
483 private var surface: Surface? = null
484 private var runWhenSurfaceIsValid = false
485
486 init {
487 // Starting state is stopped.
488 state = State.STOPPED
489 }
490
491 @get:Synchronized
492 val isStopped: Boolean
493 get() = state == State.STOPPED
494
495 // Getters for the current state
496 @get:Synchronized
497 val isPaused: Boolean
498 get() = state == State.PAUSED
499
500 @get:Synchronized
501 val isRunning: Boolean
502 get() = state == State.RUNNING
503
504 @Synchronized
505 fun stop() {
506 if (state != State.STOPPED) {
507 Log.debug("[EmulationFragment] Stopping emulation.")
508 NativeLibrary.stopEmulation()
509 state = State.STOPPED
510 } else {
511 Log.warning("[EmulationFragment] Stop called while already stopped.")
512 }
513 }
514
515 // State changing methods
516 @Synchronized
517 fun pause() {
518 if (state != State.PAUSED) {
519 Log.debug("[EmulationFragment] Pausing emulation.")
520
521 NativeLibrary.pauseEmulation()
522
523 state = State.PAUSED
524 } else {
525 Log.warning("[EmulationFragment] Pause called while already paused.")
526 }
527 }
528
529 @Synchronized
530 fun run(isActivityRecreated: Boolean) {
531 if (isActivityRecreated) {
532 if (NativeLibrary.isRunning()) {
533 state = State.PAUSED
534 }
535 } else {
536 Log.debug("[EmulationFragment] activity resumed or fresh start")
537 }
538
539 // If the surface is set, run now. Otherwise, wait for it to get set.
540 if (surface != null) {
541 runWithValidSurface()
542 } else {
543 runWhenSurfaceIsValid = true
544 }
545 }
546
547 // Surface callbacks
548 @Synchronized
549 fun newSurface(surface: Surface?) {
550 this.surface = surface
551 if (runWhenSurfaceIsValid) {
552 runWithValidSurface()
553 }
554 }
555
556 @Synchronized
557 fun clearSurface() {
558 if (surface == null) {
559 Log.warning("[EmulationFragment] clearSurface called, but surface already null.")
560 } else {
561 surface = null
562 Log.debug("[EmulationFragment] Surface destroyed.")
563 when (state) {
564 State.RUNNING -> {
565 state = State.PAUSED
566 }
567
568 State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.")
569 else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.")
570 }
571 }
572 }
573
574 private fun runWithValidSurface() {
575 runWhenSurfaceIsValid = false
576 when (state) {
577 State.STOPPED -> {
578 NativeLibrary.surfaceChanged(surface)
579 val emulationThread = Thread({
580 Log.debug("[EmulationFragment] Starting emulation thread.")
581 NativeLibrary.run(gamePath)
582 }, "NativeEmulation")
583 emulationThread.start()
584 }
585
586 State.PAUSED -> {
587 Log.debug("[EmulationFragment] Resuming emulation.")
588 NativeLibrary.surfaceChanged(surface)
589 NativeLibrary.unPauseEmulation()
590 }
591
592 else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
593 }
594 state = State.RUNNING
595 }
596
597 private enum class State {
598 STOPPED, RUNNING, PAUSED
599 }
600 }
601
602 companion object {
603 private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
604
605 fun newInstance(game: Game): EmulationFragment {
606 val args = Bundle()
607 args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game)
608 val fragment = EmulationFragment()
609 fragment.arguments = args
610 return fragment
611 }
612 }
613}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
new file mode 100644
index 000000000..536163eb6
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -0,0 +1,340 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.Manifest
7import android.content.ActivityNotFoundException
8import android.content.DialogInterface
9import android.content.Intent
10import android.content.pm.PackageManager
11import android.os.Bundle
12import android.provider.DocumentsContract
13import android.view.LayoutInflater
14import android.view.View
15import android.view.ViewGroup
16import android.view.ViewGroup.MarginLayoutParams
17import android.widget.Toast
18import androidx.appcompat.app.AppCompatActivity
19import androidx.core.app.ActivityCompat
20import androidx.core.app.NotificationCompat
21import androidx.core.app.NotificationManagerCompat
22import androidx.core.view.ViewCompat
23import androidx.core.view.WindowInsetsCompat
24import androidx.core.view.updatePadding
25import androidx.documentfile.provider.DocumentFile
26import androidx.fragment.app.Fragment
27import androidx.fragment.app.activityViewModels
28import androidx.navigation.fragment.findNavController
29import androidx.recyclerview.widget.LinearLayoutManager
30import com.google.android.material.dialog.MaterialAlertDialogBuilder
31import com.google.android.material.transition.MaterialSharedAxis
32import org.yuzu.yuzu_emu.BuildConfig
33import org.yuzu.yuzu_emu.R
34import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
35import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
36import org.yuzu.yuzu_emu.features.DocumentProvider
37import org.yuzu.yuzu_emu.features.settings.model.Settings
38import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
39import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
40import org.yuzu.yuzu_emu.model.HomeSetting
41import org.yuzu.yuzu_emu.model.HomeViewModel
42import org.yuzu.yuzu_emu.ui.main.MainActivity
43import org.yuzu.yuzu_emu.utils.FileUtil
44import org.yuzu.yuzu_emu.utils.GpuDriverHelper
45
46class HomeSettingsFragment : Fragment() {
47 private var _binding: FragmentHomeSettingsBinding? = null
48 private val binding get() = _binding!!
49
50 private lateinit var mainActivity: MainActivity
51
52 private val homeViewModel: HomeViewModel by activityViewModels()
53
54 override fun onCreate(savedInstanceState: Bundle?) {
55 super.onCreate(savedInstanceState)
56 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
57 }
58
59 override fun onCreateView(
60 inflater: LayoutInflater,
61 container: ViewGroup?,
62 savedInstanceState: Bundle?
63 ): View {
64 _binding = FragmentHomeSettingsBinding.inflate(layoutInflater)
65 return binding.root
66 }
67
68 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
69 mainActivity = requireActivity() as MainActivity
70
71 val optionsList: MutableList<HomeSetting> = mutableListOf(
72 HomeSetting(
73 R.string.advanced_settings,
74 R.string.settings_description,
75 R.drawable.ic_settings
76 ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
77 HomeSetting(
78 R.string.open_user_folder,
79 R.string.open_user_folder_description,
80 R.drawable.ic_folder_open
81 ) { openFileManager() },
82 HomeSetting(
83 R.string.preferences_theme,
84 R.string.theme_and_color_description,
85 R.drawable.ic_palette
86 ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
87 HomeSetting(
88 R.string.install_gpu_driver,
89 R.string.install_gpu_driver_description,
90 R.drawable.ic_exit
91 ) { driverInstaller() },
92 HomeSetting(
93 R.string.install_amiibo_keys,
94 R.string.install_amiibo_keys_description,
95 R.drawable.ic_nfc
96 ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
97 HomeSetting(
98 R.string.install_game_content,
99 R.string.install_game_content_description,
100 R.drawable.ic_system_update_alt
101 ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) },
102 HomeSetting(
103 R.string.select_games_folder,
104 R.string.select_games_folder_description,
105 R.drawable.ic_add
106 ) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
107 HomeSetting(
108 R.string.manage_save_data,
109 R.string.import_export_saves_description,
110 R.drawable.ic_save
111 ) {
112 ImportExportSavesFragment().show(
113 parentFragmentManager,
114 ImportExportSavesFragment.TAG
115 )
116 },
117 HomeSetting(
118 R.string.install_prod_keys,
119 R.string.install_prod_keys_description,
120 R.drawable.ic_unlock
121 ) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
122 HomeSetting(
123 R.string.install_firmware,
124 R.string.install_firmware_description,
125 R.drawable.ic_firmware
126 ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) },
127 HomeSetting(
128 R.string.share_log,
129 R.string.share_log_description,
130 R.drawable.ic_log
131 ) { shareLog() },
132 HomeSetting(
133 R.string.about,
134 R.string.about_description,
135 R.drawable.ic_info_outline
136 ) {
137 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
138 parentFragmentManager.primaryNavigationFragment?.findNavController()
139 ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
140 }
141 )
142
143 if (!BuildConfig.PREMIUM) {
144 optionsList.add(
145 0,
146 HomeSetting(
147 R.string.get_early_access,
148 R.string.get_early_access_description,
149 R.drawable.ic_diamond
150 ) {
151 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
152 parentFragmentManager.primaryNavigationFragment?.findNavController()
153 ?.navigate(R.id.action_homeSettingsFragment_to_earlyAccessFragment)
154 }
155 )
156 }
157
158 binding.homeSettingsList.apply {
159 layoutManager = LinearLayoutManager(requireContext())
160 adapter = HomeSettingAdapter(requireActivity() as AppCompatActivity, optionsList)
161 }
162
163 setInsets()
164 }
165
166 override fun onStart() {
167 super.onStart()
168 exitTransition = null
169 homeViewModel.setNavigationVisibility(visible = true, animated = true)
170 homeViewModel.setStatusBarShadeVisibility(visible = true)
171 }
172
173 override fun onDestroyView() {
174 super.onDestroyView()
175 _binding = null
176 }
177
178 private fun openFileManager() {
179 // First, try to open the user data folder directly
180 try {
181 startActivity(getFileManagerIntentOnDocumentProvider(Intent.ACTION_VIEW))
182 return
183 } catch (_: ActivityNotFoundException) {
184 }
185
186 try {
187 startActivity(getFileManagerIntentOnDocumentProvider("android.provider.action.BROWSE"))
188 return
189 } catch (_: ActivityNotFoundException) {
190 }
191
192 // Just try to open the file manager, try the package name used on "normal" phones
193 try {
194 startActivity(getFileManagerIntent("com.google.android.documentsui"))
195 showNoLinkNotification()
196 return
197 } catch (_: ActivityNotFoundException) {
198 }
199
200 try {
201 // Next, try the AOSP package name
202 startActivity(getFileManagerIntent("com.android.documentsui"))
203 showNoLinkNotification()
204 return
205 } catch (_: ActivityNotFoundException) {
206 }
207
208 Toast.makeText(
209 requireContext(),
210 resources.getString(R.string.no_file_manager),
211 Toast.LENGTH_LONG
212 ).show()
213 }
214
215 private fun getFileManagerIntent(packageName: String): Intent {
216 // Fragile, but some phones don't expose the system file manager in any better way
217 val intent = Intent(Intent.ACTION_MAIN)
218 intent.setClassName(packageName, "com.android.documentsui.files.FilesActivity")
219 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
220 return intent
221 }
222
223 private fun getFileManagerIntentOnDocumentProvider(action: String): Intent {
224 val authority = "${requireContext().packageName}.user"
225 val intent = Intent(action)
226 intent.addCategory(Intent.CATEGORY_DEFAULT)
227 intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID)
228 intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
229 return intent
230 }
231
232 private fun showNoLinkNotification() {
233 val builder = NotificationCompat.Builder(
234 requireContext(),
235 getString(R.string.notice_notification_channel_id)
236 )
237 .setSmallIcon(R.drawable.ic_stat_notification_logo)
238 .setContentTitle(getString(R.string.notification_no_directory_link))
239 .setContentText(getString(R.string.notification_no_directory_link_description))
240 .setPriority(NotificationCompat.PRIORITY_HIGH)
241 .setAutoCancel(true)
242 // TODO: Make the click action for this notification lead to a help article
243
244 with(NotificationManagerCompat.from(requireContext())) {
245 if (ActivityCompat.checkSelfPermission(
246 requireContext(),
247 Manifest.permission.POST_NOTIFICATIONS
248 ) != PackageManager.PERMISSION_GRANTED
249 ) {
250 Toast.makeText(
251 requireContext(),
252 resources.getString(R.string.notification_permission_not_granted),
253 Toast.LENGTH_LONG
254 ).show()
255 return
256 }
257 notify(0, builder.build())
258 }
259 }
260
261 private fun driverInstaller() {
262 // Get the driver name for the dialog message.
263 var driverName = GpuDriverHelper.customDriverName
264 if (driverName == null) {
265 driverName = getString(R.string.system_gpu_driver)
266 }
267
268 MaterialAlertDialogBuilder(requireContext())
269 .setTitle(getString(R.string.select_gpu_driver_title))
270 .setMessage(driverName)
271 .setNegativeButton(android.R.string.cancel, null)
272 .setNeutralButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
273 GpuDriverHelper.installDefaultDriver(requireContext())
274 Toast.makeText(
275 requireContext(),
276 R.string.select_gpu_driver_use_default,
277 Toast.LENGTH_SHORT
278 ).show()
279 }
280 .setPositiveButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
281 mainActivity.getDriver.launch(arrayOf("application/zip"))
282 }
283 .show()
284 }
285
286 private fun shareLog() {
287 val file = DocumentFile.fromSingleUri(
288 mainActivity,
289 DocumentsContract.buildDocumentUri(
290 DocumentProvider.AUTHORITY,
291 "${DocumentProvider.ROOT_ID}/log/yuzu_log.txt"
292 )
293 )!!
294 if (file.exists()) {
295 val intent = Intent(Intent.ACTION_SEND)
296 .setDataAndType(file.uri, FileUtil.TEXT_PLAIN)
297 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
298 .putExtra(Intent.EXTRA_STREAM, file.uri)
299 startActivity(Intent.createChooser(intent, getText(R.string.share_log)))
300 } else {
301 Toast.makeText(
302 requireContext(),
303 getText(R.string.share_log_missing),
304 Toast.LENGTH_SHORT
305 ).show()
306 }
307 }
308
309 private fun setInsets() =
310 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
311 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
312 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
313 val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
314 val spacingNavigationRail =
315 resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
316
317 val leftInsets = barInsets.left + cutoutInsets.left
318 val rightInsets = barInsets.right + cutoutInsets.right
319
320 binding.scrollViewSettings.updatePadding(
321 top = barInsets.top,
322 bottom = barInsets.bottom
323 )
324
325 val mlpScrollSettings = binding.scrollViewSettings.layoutParams as MarginLayoutParams
326 mlpScrollSettings.leftMargin = leftInsets
327 mlpScrollSettings.rightMargin = rightInsets
328 binding.scrollViewSettings.layoutParams = mlpScrollSettings
329
330 binding.linearLayoutSettings.updatePadding(bottom = spacingNavigation)
331
332 if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
333 binding.linearLayoutSettings.updatePadding(left = spacingNavigationRail)
334 } else {
335 binding.linearLayoutSettings.updatePadding(right = spacingNavigationRail)
336 }
337
338 windowInsets
339 }
340}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
new file mode 100644
index 000000000..36e63bb9e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
@@ -0,0 +1,210 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.Intent
8import android.net.Uri
9import android.os.Bundle
10import android.provider.DocumentsContract
11import android.widget.Toast
12import androidx.activity.result.ActivityResultLauncher
13import androidx.activity.result.contract.ActivityResultContracts
14import androidx.appcompat.app.AppCompatActivity
15import androidx.documentfile.provider.DocumentFile
16import androidx.fragment.app.DialogFragment
17import com.google.android.material.dialog.MaterialAlertDialogBuilder
18import kotlinx.coroutines.CoroutineScope
19import kotlinx.coroutines.Dispatchers
20import kotlinx.coroutines.launch
21import kotlinx.coroutines.withContext
22import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.YuzuApplication
24import org.yuzu.yuzu_emu.features.DocumentProvider
25import org.yuzu.yuzu_emu.getPublicFilesDir
26import org.yuzu.yuzu_emu.utils.FileUtil
27import java.io.BufferedOutputStream
28import java.io.File
29import java.io.FileOutputStream
30import java.io.FilenameFilter
31import java.time.LocalDateTime
32import java.time.format.DateTimeFormatter
33import java.util.zip.ZipEntry
34import java.util.zip.ZipOutputStream
35
36class ImportExportSavesFragment : DialogFragment() {
37 private val context = YuzuApplication.appContext
38 private val savesFolder =
39 "${context.getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
40
41 // Get first subfolder in saves folder (should be the user folder)
42 private val savesFolderRoot = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
43 private var lastZipCreated: File? = null
44
45 private lateinit var startForResultExportSave: ActivityResultLauncher<Intent>
46 private lateinit var documentPicker: ActivityResultLauncher<Array<String>>
47
48 override fun onCreate(savedInstanceState: Bundle?) {
49 super.onCreate(savedInstanceState)
50 val activity = requireActivity() as AppCompatActivity
51
52 val activityResultRegistry = requireActivity().activityResultRegistry
53 startForResultExportSave = activityResultRegistry.register(
54 "startForResultExportSaveKey",
55 ActivityResultContracts.StartActivityForResult()
56 ) {
57 File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
58 }
59 documentPicker = activityResultRegistry.register(
60 "documentPickerKey",
61 ActivityResultContracts.OpenDocument()
62 ) {
63 it?.let { uri -> importSave(uri, activity) }
64 }
65 }
66
67 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
68 return if (savesFolderRoot == "") {
69 MaterialAlertDialogBuilder(requireContext())
70 .setTitle(R.string.manage_save_data)
71 .setMessage(R.string.import_export_saves_no_profile)
72 .setPositiveButton(android.R.string.ok, null)
73 .show()
74 } else {
75 MaterialAlertDialogBuilder(requireContext())
76 .setTitle(R.string.manage_save_data)
77 .setMessage(R.string.manage_save_data_description)
78 .setNegativeButton(R.string.export_saves) { _, _ ->
79 exportSave()
80 }
81 .setPositiveButton(R.string.import_saves) { _, _ ->
82 documentPicker.launch(arrayOf("application/zip"))
83 }
84 .setNeutralButton(android.R.string.cancel, null)
85 .show()
86 }
87 }
88
89 /**
90 * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
91 * @return true if the zip file is successfully created, false otherwise.
92 */
93 private fun zipSave(): Boolean {
94 try {
95 val tempFolder = File(requireContext().getPublicFilesDir().canonicalPath, "temp")
96 tempFolder.mkdirs()
97 val saveFolder = File(savesFolderRoot)
98 val outputZipFile = File(
99 tempFolder,
100 "yuzu saves - ${
101 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
102 }.zip"
103 )
104 outputZipFile.createNewFile()
105 ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
106 saveFolder.walkTopDown().forEach { file ->
107 val zipFileName =
108 file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
109 if (zipFileName == "")
110 return@forEach
111 val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
112 zos.putNextEntry(entry)
113 if (file.isFile)
114 file.inputStream().use { fis -> fis.copyTo(zos) }
115 }
116 }
117 lastZipCreated = outputZipFile
118 } catch (e: Exception) {
119 return false
120 }
121 return true
122 }
123
124 /**
125 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
126 */
127 private fun exportSave() {
128 CoroutineScope(Dispatchers.IO).launch {
129 val wasZipCreated = zipSave()
130 val lastZipFile = lastZipCreated
131 if (!wasZipCreated || lastZipFile == null) {
132 withContext(Dispatchers.Main) {
133 Toast.makeText(context, "Failed to export save", Toast.LENGTH_LONG).show()
134 }
135 return@launch
136 }
137
138 withContext(Dispatchers.Main) {
139 val file = DocumentFile.fromSingleUri(
140 context, DocumentsContract.buildDocumentUri(
141 DocumentProvider.AUTHORITY,
142 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
143 )
144 )!!
145 val intent = Intent(Intent.ACTION_SEND)
146 .setDataAndType(file.uri, "application/zip")
147 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
148 .putExtra(Intent.EXTRA_STREAM, file.uri)
149 startForResultExportSave.launch(Intent.createChooser(intent, "Share save file"))
150 }
151 }
152 }
153
154 /**
155 * Imports the save files contained in the zip file, and replaces any existing ones with the new save file.
156 * @param zipUri The Uri of the zip file containing the save file(s) to import.
157 */
158 private fun importSave(zipUri: Uri, activity: AppCompatActivity) {
159 val inputZip = context.contentResolver.openInputStream(zipUri)
160 // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
161 var validZip = false
162 val savesFolder = File(savesFolderRoot)
163 val cacheSaveDir = File("${context.cacheDir.path}/saves/")
164 cacheSaveDir.mkdir()
165
166 if (inputZip == null) {
167 Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
168 .show()
169 return
170 }
171
172 val filterTitleId =
173 FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
174
175 try {
176 CoroutineScope(Dispatchers.IO).launch {
177 FileUtil.unzip(inputZip, cacheSaveDir)
178 cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
179 File(savesFolder, savePath).deleteRecursively()
180 File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true)
181 validZip = true
182 }
183
184 withContext(Dispatchers.Main) {
185 if (!validZip) {
186 MessageDialogFragment.newInstance(
187 R.string.save_file_invalid_zip_structure,
188 R.string.save_file_invalid_zip_structure_description
189 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
190 return@withContext
191 }
192 Toast.makeText(
193 context,
194 context.getString(R.string.save_file_imported_success),
195 Toast.LENGTH_LONG
196 ).show()
197 }
198
199 cacheSaveDir.deleteRecursively()
200 }
201 } catch (e: Exception) {
202 Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
203 .show()
204 }
205 }
206
207 companion object {
208 const val TAG = "ImportExportSavesFragment"
209 }
210}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
new file mode 100644
index 000000000..c7880d8cc
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.os.Bundle
8import android.widget.Toast
9import androidx.appcompat.app.AppCompatActivity
10import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels
12import androidx.lifecycle.ViewModelProvider
13import com.google.android.material.dialog.MaterialAlertDialogBuilder
14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
15import org.yuzu.yuzu_emu.model.TaskViewModel
16
17
18class IndeterminateProgressDialogFragment : DialogFragment() {
19 private val taskViewModel: TaskViewModel by activityViewModels()
20
21 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
22 val titleId = requireArguments().getInt(TITLE)
23
24 val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
25 progressBinding.progressBar.isIndeterminate = true
26 val dialog = MaterialAlertDialogBuilder(requireContext())
27 .setTitle(titleId)
28 .setView(progressBinding.root)
29 .create()
30 dialog.setCanceledOnTouchOutside(false)
31
32 taskViewModel.isComplete.observe(this) { complete ->
33 if (complete) {
34 dialog.dismiss()
35 when (val result = taskViewModel.result.value) {
36 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
37 is MessageDialogFragment -> result.show(
38 parentFragmentManager,
39 MessageDialogFragment.TAG
40 )
41 }
42 taskViewModel.clear()
43 }
44 }
45
46 if (taskViewModel.isRunning.value == false) {
47 taskViewModel.runTask()
48 }
49 return dialog
50 }
51
52 companion object {
53 const val TAG = "IndeterminateProgressDialogFragment"
54
55 private const val TITLE = "Title"
56
57 fun newInstance(
58 activity: AppCompatActivity,
59 titleId: Int,
60 task: () -> Any
61 ): IndeterminateProgressDialogFragment {
62 val dialog = IndeterminateProgressDialogFragment()
63 val args = Bundle()
64 ViewModelProvider(activity)[TaskViewModel::class.java].task = task
65 args.putInt(TITLE, titleId)
66 dialog.arguments = args
67 return dialog
68 }
69 }
70}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicenseBottomSheetDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicenseBottomSheetDialogFragment.kt
new file mode 100644
index 000000000..78419191c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicenseBottomSheetDialogFragment.kt
@@ -0,0 +1,59 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.os.Bundle
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import com.google.android.material.bottomsheet.BottomSheetBehavior
11import com.google.android.material.bottomsheet.BottomSheetDialogFragment
12import org.yuzu.yuzu_emu.databinding.DialogLicenseBinding
13import org.yuzu.yuzu_emu.model.License
14import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
15
16class LicenseBottomSheetDialogFragment : BottomSheetDialogFragment() {
17 private var _binding: DialogLicenseBinding? = null
18 private val binding get() = _binding!!
19
20 override fun onCreateView(
21 inflater: LayoutInflater,
22 container: ViewGroup?,
23 savedInstanceState: Bundle?
24 ): View {
25 _binding = DialogLicenseBinding.inflate(layoutInflater)
26 return binding.root
27 }
28
29 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30 super.onViewCreated(view, savedInstanceState)
31 BottomSheetBehavior.from<View>(view.parent as View).state =
32 BottomSheetBehavior.STATE_HALF_EXPANDED
33
34 val license = requireArguments().parcelable<License>(LICENSE)!!
35
36 binding.apply {
37 textTitle.setText(license.titleId)
38 textLink.setText(license.linkId)
39 textCopyright.setText(license.copyrightId)
40 textLicense.setText(license.licenseId)
41 }
42 }
43
44 companion object {
45 const val TAG = "LicenseBottomSheetDialogFragment"
46
47 const val LICENSE = "License"
48
49 fun newInstance(
50 license: License
51 ): LicenseBottomSheetDialogFragment {
52 val dialog = LicenseBottomSheetDialogFragment()
53 val bundle = Bundle()
54 bundle.putParcelable(LICENSE, license)
55 dialog.arguments = bundle
56 return dialog
57 }
58 }
59}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt
new file mode 100644
index 000000000..59141e823
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt
@@ -0,0 +1,137 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.os.Bundle
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import android.view.ViewGroup.MarginLayoutParams
11import androidx.appcompat.app.AppCompatActivity
12import androidx.core.view.ViewCompat
13import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding
15import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels
17import androidx.navigation.findNavController
18import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.transition.MaterialSharedAxis
20import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.adapters.LicenseAdapter
22import org.yuzu.yuzu_emu.databinding.FragmentLicensesBinding
23import org.yuzu.yuzu_emu.model.HomeViewModel
24import org.yuzu.yuzu_emu.model.License
25
26class LicensesFragment : Fragment() {
27 private var _binding: FragmentLicensesBinding? = null
28 private val binding get() = _binding!!
29
30 private val homeViewModel: HomeViewModel by activityViewModels()
31
32 override fun onCreate(savedInstanceState: Bundle?) {
33 super.onCreate(savedInstanceState)
34 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
35 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
36 }
37
38 override fun onCreateView(
39 inflater: LayoutInflater,
40 container: ViewGroup?,
41 savedInstanceState: Bundle?
42 ): View {
43 _binding = FragmentLicensesBinding.inflate(layoutInflater)
44 return binding.root
45 }
46
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 homeViewModel.setNavigationVisibility(visible = false, animated = true)
49 homeViewModel.setStatusBarShadeVisibility(visible = false)
50
51 binding.toolbarLicenses.setNavigationOnClickListener {
52 binding.root.findNavController().popBackStack()
53 }
54
55 val licenses = listOf(
56 License(
57 R.string.license_fidelityfx_fsr,
58 R.string.license_fidelityfx_fsr_description,
59 R.string.license_fidelityfx_fsr_link,
60 R.string.license_fidelityfx_fsr_copyright,
61 R.string.license_fidelityfx_fsr_text
62 ),
63 License(
64 R.string.license_cubeb,
65 R.string.license_cubeb_description,
66 R.string.license_cubeb_link,
67 R.string.license_cubeb_copyright,
68 R.string.license_cubeb_text
69 ),
70 License(
71 R.string.license_dynarmic,
72 R.string.license_dynarmic_description,
73 R.string.license_dynarmic_link,
74 R.string.license_dynarmic_copyright,
75 R.string.license_dynarmic_text
76 ),
77 License(
78 R.string.license_ffmpeg,
79 R.string.license_ffmpeg_description,
80 R.string.license_ffmpeg_link,
81 R.string.license_ffmpeg_copyright,
82 R.string.license_ffmpeg_text
83 ),
84 License(
85 R.string.license_opus,
86 R.string.license_opus_description,
87 R.string.license_opus_link,
88 R.string.license_opus_copyright,
89 R.string.license_opus_text
90 ),
91 License(
92 R.string.license_sirit,
93 R.string.license_sirit_description,
94 R.string.license_sirit_link,
95 R.string.license_sirit_copyright,
96 R.string.license_sirit_text
97 ),
98 License(
99 R.string.license_adreno_tools,
100 R.string.license_adreno_tools_description,
101 R.string.license_adreno_tools_link,
102 R.string.license_adreno_tools_copyright,
103 R.string.license_adreno_tools_text
104 )
105 )
106
107 binding.listLicenses.apply {
108 layoutManager = LinearLayoutManager(requireContext())
109 adapter = LicenseAdapter(requireActivity() as AppCompatActivity, licenses)
110 }
111
112 setInsets()
113 }
114
115 private fun setInsets() =
116 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
117 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
118 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
119
120 val leftInsets = barInsets.left + cutoutInsets.left
121 val rightInsets = barInsets.right + cutoutInsets.right
122
123 val mlpAppBar = binding.appbarLicenses.layoutParams as MarginLayoutParams
124 mlpAppBar.leftMargin = leftInsets
125 mlpAppBar.rightMargin = rightInsets
126 binding.appbarLicenses.layoutParams = mlpAppBar
127
128 val mlpScrollAbout = binding.listLicenses.layoutParams as MarginLayoutParams
129 mlpScrollAbout.leftMargin = leftInsets
130 mlpScrollAbout.rightMargin = rightInsets
131 binding.listLicenses.layoutParams = mlpScrollAbout
132
133 binding.listLicenses.updatePadding(bottom = barInsets.bottom)
134
135 windowInsets
136 }
137}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
new file mode 100644
index 000000000..2db38fdc2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
@@ -0,0 +1,62 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.Intent
8import android.net.Uri
9import android.os.Bundle
10import androidx.fragment.app.DialogFragment
11import com.google.android.material.dialog.MaterialAlertDialogBuilder
12import org.yuzu.yuzu_emu.R
13
14class MessageDialogFragment : DialogFragment() {
15 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
16 val titleId = requireArguments().getInt(TITLE)
17 val descriptionId = requireArguments().getInt(DESCRIPTION)
18 val helpLinkId = requireArguments().getInt(HELP_LINK)
19
20 val dialog = MaterialAlertDialogBuilder(requireContext())
21 .setPositiveButton(R.string.close, null)
22 .setTitle(titleId)
23 .setMessage(descriptionId)
24
25 if (helpLinkId != 0) {
26 dialog.setNeutralButton(R.string.learn_more) { _, _ ->
27 openLink(getString(helpLinkId))
28 }
29 }
30
31 return dialog.show()
32 }
33
34 private fun openLink(link: String) {
35 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
36 startActivity(intent)
37 }
38
39 companion object {
40 const val TAG = "MessageDialogFragment"
41
42 private const val TITLE = "Title"
43 private const val DESCRIPTION = "Description"
44 private const val HELP_LINK = "Link"
45
46 fun newInstance(
47 titleId: Int,
48 descriptionId: Int,
49 helpLinkId: Int = 0
50 ): MessageDialogFragment {
51 val dialog = MessageDialogFragment()
52 val bundle = Bundle()
53 bundle.apply {
54 putInt(TITLE, titleId)
55 putInt(DESCRIPTION, descriptionId)
56 putInt(HELP_LINK, helpLinkId)
57 }
58 dialog.arguments = bundle
59 return dialog
60 }
61 }
62}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/PermissionDeniedDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/PermissionDeniedDialogFragment.kt
new file mode 100644
index 000000000..3478b9250
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/PermissionDeniedDialogFragment.kt
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.content.Intent
9import android.net.Uri
10import android.os.Bundle
11import android.provider.Settings
12import androidx.fragment.app.DialogFragment
13import com.google.android.material.dialog.MaterialAlertDialogBuilder
14import org.yuzu.yuzu_emu.R
15
16class PermissionDeniedDialogFragment : DialogFragment() {
17 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
18 return MaterialAlertDialogBuilder(requireContext())
19 .setPositiveButton(R.string.home_settings) { _: DialogInterface?, _: Int ->
20 openSettings()
21 }
22 .setNegativeButton(android.R.string.cancel, null)
23 .setTitle(R.string.permission_denied)
24 .setMessage(R.string.permission_denied_description)
25 .show()
26 }
27
28 private fun openSettings() {
29 val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
30 val uri = Uri.fromParts("package", requireActivity().packageName, null)
31 intent.data = uri
32 startActivity(intent)
33 }
34
35 companion object {
36 const val TAG = "PermissionDeniedDialogFragment"
37 }
38}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ResetSettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ResetSettingsDialogFragment.kt
new file mode 100644
index 000000000..1b4b93ab8
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ResetSettingsDialogFragment.kt
@@ -0,0 +1,30 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.os.Bundle
8import androidx.fragment.app.DialogFragment
9import com.google.android.material.dialog.MaterialAlertDialogBuilder
10import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
12
13class ResetSettingsDialogFragment : DialogFragment() {
14 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
15 val settingsActivity = requireActivity() as SettingsActivity
16
17 return MaterialAlertDialogBuilder(requireContext())
18 .setTitle(R.string.reset_all_settings)
19 .setMessage(R.string.reset_all_settings_description)
20 .setPositiveButton(android.R.string.ok) { _, _ ->
21 settingsActivity.onSettingsReset()
22 }
23 .setNegativeButton(android.R.string.cancel, null)
24 .show()
25 }
26
27 companion object {
28 const val TAG = "ResetSettingsDialogFragment"
29 }
30}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
new file mode 100644
index 000000000..adbe3696b
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -0,0 +1,230 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.content.Context
7import android.content.SharedPreferences
8import android.os.Bundle
9import android.view.LayoutInflater
10import android.view.View
11import android.view.ViewGroup
12import android.view.inputmethod.InputMethodManager
13import androidx.appcompat.app.AppCompatActivity
14import androidx.core.view.ViewCompat
15import androidx.core.view.WindowInsetsCompat
16import androidx.core.view.updatePadding
17import androidx.core.widget.doOnTextChanged
18import androidx.fragment.app.Fragment
19import androidx.fragment.app.activityViewModels
20import androidx.preference.PreferenceManager
21import info.debatty.java.stringsimilarity.Jaccard
22import info.debatty.java.stringsimilarity.JaroWinkler
23import org.yuzu.yuzu_emu.R
24import org.yuzu.yuzu_emu.YuzuApplication
25import org.yuzu.yuzu_emu.adapters.GameAdapter
26import org.yuzu.yuzu_emu.databinding.FragmentSearchBinding
27import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
28import org.yuzu.yuzu_emu.model.Game
29import org.yuzu.yuzu_emu.model.GamesViewModel
30import org.yuzu.yuzu_emu.model.HomeViewModel
31import org.yuzu.yuzu_emu.utils.FileUtil
32import org.yuzu.yuzu_emu.utils.Log
33import java.util.Locale
34
35class SearchFragment : Fragment() {
36 private var _binding: FragmentSearchBinding? = null
37 private val binding get() = _binding!!
38
39 private val gamesViewModel: GamesViewModel by activityViewModels()
40 private val homeViewModel: HomeViewModel by activityViewModels()
41
42 private lateinit var preferences: SharedPreferences
43
44 companion object {
45 private const val SEARCH_TEXT = "SearchText"
46 }
47
48 override fun onCreateView(
49 inflater: LayoutInflater,
50 container: ViewGroup?,
51 savedInstanceState: Bundle?
52 ): View {
53 _binding = FragmentSearchBinding.inflate(layoutInflater)
54 return binding.root
55 }
56
57 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
58 homeViewModel.setNavigationVisibility(visible = true, animated = false)
59 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
60
61 if (savedInstanceState != null) {
62 binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
63 }
64
65 binding.gridGamesSearch.apply {
66 layoutManager = AutofitGridLayoutManager(
67 requireContext(),
68 requireContext().resources.getDimensionPixelSize(R.dimen.card_width)
69 )
70 adapter = GameAdapter(requireActivity() as AppCompatActivity)
71 }
72
73 binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() }
74
75 binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int ->
76 if (text.toString().isNotEmpty()) {
77 binding.clearButton.visibility = View.VISIBLE
78 } else {
79 binding.clearButton.visibility = View.INVISIBLE
80 }
81 filterAndSearch()
82 }
83
84 gamesViewModel.apply {
85 searchFocused.observe(viewLifecycleOwner) { searchFocused ->
86 if (searchFocused) {
87 focusSearch()
88 gamesViewModel.setSearchFocused(false)
89 }
90 }
91
92 games.observe(viewLifecycleOwner) { filterAndSearch() }
93 searchedGames.observe(viewLifecycleOwner) {
94 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
95 if (it.isEmpty()) {
96 binding.noResultsView.visibility = View.VISIBLE
97 } else {
98 binding.noResultsView.visibility = View.GONE
99 }
100 }
101 }
102
103 binding.clearButton.setOnClickListener { binding.searchText.setText("") }
104
105 binding.searchBackground.setOnClickListener { focusSearch() }
106
107 setInsets()
108 filterAndSearch()
109 }
110
111 private inner class ScoredGame(val score: Double, val item: Game)
112
113 private fun filterAndSearch() {
114 val baseList = gamesViewModel.games.value!!
115 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
116 R.id.chip_recently_played -> {
117 baseList.filter {
118 val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L)
119 lastPlayedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
120 }
121 }
122
123 R.id.chip_recently_added -> {
124 baseList.filter {
125 val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L)
126 addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
127 }
128 }
129
130 R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
131
132 R.id.chip_retail -> baseList.filter {
133 FileUtil.hasExtension(it.path, "xci")
134 || FileUtil.hasExtension(it.path, "nsp")
135 }
136
137 else -> baseList
138 }
139
140 if (binding.searchText.text.toString().isEmpty()
141 && binding.chipGroup.checkedChipId != View.NO_ID
142 ) {
143 gamesViewModel.setSearchedGames(filteredList)
144 return
145 }
146
147 val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
148 val searchAlgorithm = if (searchTerm.length > 1) Jaccard(2) else JaroWinkler()
149 val sortedList: List<Game> = filteredList.mapNotNull { game ->
150 val title = game.title.lowercase(Locale.getDefault())
151 val score = searchAlgorithm.similarity(searchTerm, title)
152 if (score > 0.03) {
153 ScoredGame(score, game)
154 } else {
155 null
156 }
157 }.sortedByDescending { it.score }.map { it.item }
158 gamesViewModel.setSearchedGames(sortedList)
159 }
160
161 override fun onDestroyView() {
162 super.onDestroyView()
163 _binding = null
164 }
165
166 override fun onSaveInstanceState(outState: Bundle) {
167 super.onSaveInstanceState(outState)
168 if (_binding != null) {
169 outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
170 }
171 }
172
173 private fun focusSearch() {
174 if (_binding != null) {
175 binding.searchText.requestFocus()
176 val imm =
177 requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
178 imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
179 }
180 }
181
182 private fun setInsets() =
183 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
184 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
185 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
186 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
187 val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
188 val spacingNavigationRail =
189 resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
190 val chipSpacing = resources.getDimensionPixelSize(R.dimen.spacing_chip)
191
192 binding.constraintSearch.updatePadding(
193 left = barInsets.left + cutoutInsets.left,
194 top = barInsets.top,
195 right = barInsets.right + cutoutInsets.right
196 )
197
198 binding.gridGamesSearch.updatePadding(
199 top = extraListSpacing,
200 bottom = barInsets.bottom + spacingNavigation + extraListSpacing
201 )
202 binding.noResultsView.updatePadding(bottom = spacingNavigation + barInsets.bottom)
203
204 val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
205 if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
206 binding.frameSearch.updatePadding(left = spacingNavigationRail)
207 binding.gridGamesSearch.updatePadding(left = spacingNavigationRail)
208 binding.noResultsView.updatePadding(left = spacingNavigationRail)
209 binding.chipGroup.updatePadding(
210 left = chipSpacing + spacingNavigationRail,
211 right = chipSpacing
212 )
213 mlpDivider.leftMargin = chipSpacing + spacingNavigationRail
214 mlpDivider.rightMargin = chipSpacing
215 } else {
216 binding.frameSearch.updatePadding(right = spacingNavigationRail)
217 binding.gridGamesSearch.updatePadding(right = spacingNavigationRail)
218 binding.noResultsView.updatePadding(right = spacingNavigationRail)
219 binding.chipGroup.updatePadding(
220 left = chipSpacing,
221 right = chipSpacing + spacingNavigationRail
222 )
223 mlpDivider.leftMargin = chipSpacing
224 mlpDivider.rightMargin = chipSpacing + spacingNavigationRail
225 }
226 binding.divider.layoutParams = mlpDivider
227
228 windowInsets
229 }
230}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
new file mode 100644
index 000000000..258773380
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -0,0 +1,329 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.Manifest
7import android.content.Intent
8import android.os.Build
9import android.os.Bundle
10import android.view.LayoutInflater
11import android.view.View
12import android.view.ViewGroup
13import androidx.activity.OnBackPressedCallback
14import androidx.activity.result.contract.ActivityResultContracts
15import androidx.annotation.RequiresApi
16import androidx.appcompat.app.AppCompatActivity
17import androidx.core.app.NotificationManagerCompat
18import androidx.core.content.ContextCompat
19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowInsetsCompat
21import androidx.core.view.isVisible
22import androidx.fragment.app.Fragment
23import androidx.fragment.app.activityViewModels
24import androidx.navigation.findNavController
25import androidx.preference.PreferenceManager
26import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
27import com.google.android.material.transition.MaterialFadeThrough
28import org.yuzu.yuzu_emu.R
29import org.yuzu.yuzu_emu.YuzuApplication
30import org.yuzu.yuzu_emu.adapters.SetupAdapter
31import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding
32import org.yuzu.yuzu_emu.features.settings.model.Settings
33import org.yuzu.yuzu_emu.model.HomeViewModel
34import org.yuzu.yuzu_emu.model.SetupPage
35import org.yuzu.yuzu_emu.ui.main.MainActivity
36import org.yuzu.yuzu_emu.utils.DirectoryInitialization
37import org.yuzu.yuzu_emu.utils.GameHelper
38import java.io.File
39
40class SetupFragment : Fragment() {
41 private var _binding: FragmentSetupBinding? = null
42 private val binding get() = _binding!!
43
44 private val homeViewModel: HomeViewModel by activityViewModels()
45
46 private lateinit var mainActivity: MainActivity
47
48 private lateinit var hasBeenWarned: BooleanArray
49
50 companion object {
51 const val KEY_NEXT_VISIBILITY = "NextButtonVisibility"
52 const val KEY_BACK_VISIBILITY = "BackButtonVisibility"
53 const val KEY_HAS_BEEN_WARNED = "HasBeenWarned"
54 }
55
56 override fun onCreate(savedInstanceState: Bundle?) {
57 super.onCreate(savedInstanceState)
58 exitTransition = MaterialFadeThrough()
59 }
60
61 override fun onCreateView(
62 inflater: LayoutInflater,
63 container: ViewGroup?,
64 savedInstanceState: Bundle?
65 ): View {
66 _binding = FragmentSetupBinding.inflate(layoutInflater)
67 return binding.root
68 }
69
70 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
71 mainActivity = requireActivity() as MainActivity
72
73 homeViewModel.setNavigationVisibility(visible = false, animated = false)
74
75 requireActivity().onBackPressedDispatcher.addCallback(
76 viewLifecycleOwner,
77 object : OnBackPressedCallback(true) {
78 override fun handleOnBackPressed() {
79 if (binding.viewPager2.currentItem > 0) {
80 pageBackward()
81 } else {
82 requireActivity().finish()
83 }
84 }
85 })
86
87 requireActivity().window.navigationBarColor =
88 ContextCompat.getColor(requireContext(), android.R.color.transparent)
89
90 val pages = mutableListOf<SetupPage>()
91 pages.apply {
92 add(
93 SetupPage(
94 R.drawable.ic_yuzu_title,
95 R.string.welcome,
96 R.string.welcome_description,
97 0,
98 true,
99 R.string.get_started,
100 { pageForward() },
101 false
102 )
103 )
104
105 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
106 add(
107 SetupPage(
108 R.drawable.ic_notification,
109 R.string.notifications,
110 R.string.notifications_description,
111 0,
112 false,
113 R.string.give_permission,
114 { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) },
115 true,
116 R.string.notification_warning,
117 R.string.notification_warning_description,
118 0,
119 {
120 NotificationManagerCompat.from(requireContext())
121 .areNotificationsEnabled()
122 }
123 )
124 )
125 }
126
127 add(
128 SetupPage(
129 R.drawable.ic_key,
130 R.string.keys,
131 R.string.keys_description,
132 R.drawable.ic_add,
133 true,
134 R.string.select_keys,
135 { mainActivity.getProdKey.launch(arrayOf("*/*")) },
136 true,
137 R.string.install_prod_keys_warning,
138 R.string.install_prod_keys_warning_description,
139 R.string.install_prod_keys_warning_help,
140 { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() }
141 )
142 )
143 add(
144 SetupPage(
145 R.drawable.ic_controller,
146 R.string.games,
147 R.string.games_description,
148 R.drawable.ic_add,
149 true,
150 R.string.add_games,
151 { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
152 true,
153 R.string.add_games_warning,
154 R.string.add_games_warning_description,
155 R.string.add_games_warning_help,
156 {
157 val preferences =
158 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
159 preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
160 }
161 )
162 )
163 add(
164 SetupPage(
165 R.drawable.ic_check,
166 R.string.done,
167 R.string.done_description,
168 R.drawable.ic_arrow_forward,
169 false,
170 R.string.text_continue,
171 { finishSetup() },
172 false
173 )
174 )
175 }
176
177 binding.viewPager2.apply {
178 adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
179 offscreenPageLimit = 2
180 isUserInputEnabled = false
181 }
182
183 binding.viewPager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
184 var previousPosition: Int = 0
185
186 override fun onPageSelected(position: Int) {
187 super.onPageSelected(position)
188
189 if (position == 1 && previousPosition == 0) {
190 showView(binding.buttonNext)
191 showView(binding.buttonBack)
192 } else if (position == 0 && previousPosition == 1) {
193 hideView(binding.buttonBack)
194 hideView(binding.buttonNext)
195 } else if (position == pages.size - 1 && previousPosition == pages.size - 2) {
196 hideView(binding.buttonNext)
197 } else if (position == pages.size - 2 && previousPosition == pages.size - 1) {
198 showView(binding.buttonNext)
199 }
200
201 previousPosition = position
202 }
203 })
204
205 binding.buttonNext.setOnClickListener {
206 val index = binding.viewPager2.currentItem
207 val currentPage = pages[index]
208
209 // Checks if the user has completed the task on the current page
210 if (currentPage.hasWarning) {
211 if (currentPage.taskCompleted.invoke()) {
212 pageForward()
213 return@setOnClickListener
214 }
215
216 if (!hasBeenWarned[index]) {
217 SetupWarningDialogFragment.newInstance(
218 currentPage.warningTitleId,
219 currentPage.warningDescriptionId,
220 currentPage.warningHelpLinkId,
221 index
222 ).show(childFragmentManager, SetupWarningDialogFragment.TAG)
223 return@setOnClickListener
224 }
225 }
226 pageForward()
227 }
228 binding.buttonBack.setOnClickListener { pageBackward() }
229
230 if (savedInstanceState != null) {
231 val nextIsVisible = savedInstanceState.getBoolean(KEY_NEXT_VISIBILITY)
232 val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY)
233 hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!!
234
235 if (nextIsVisible) {
236 binding.buttonNext.visibility = View.VISIBLE
237 }
238 if (backIsVisible) {
239 binding.buttonBack.visibility = View.VISIBLE
240 }
241 } else {
242 hasBeenWarned = BooleanArray(pages.size)
243 }
244
245 setInsets()
246 }
247
248 override fun onSaveInstanceState(outState: Bundle) {
249 super.onSaveInstanceState(outState)
250 outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible)
251 outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible)
252 outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned)
253 }
254
255 override fun onDestroyView() {
256 super.onDestroyView()
257 _binding = null
258 }
259
260 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
261 private val permissionLauncher =
262 registerForActivityResult(ActivityResultContracts.RequestPermission()) {
263 if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
264 PermissionDeniedDialogFragment().show(
265 childFragmentManager,
266 PermissionDeniedDialogFragment.TAG
267 )
268 }
269 }
270
271 private fun finishSetup() {
272 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
273 .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
274 .apply()
275 mainActivity.finishSetup(binding.root.findNavController())
276 }
277
278 private fun showView(view: View) {
279 view.apply {
280 alpha = 0f
281 visibility = View.VISIBLE
282 isClickable = true
283 }.animate().apply {
284 duration = 300
285 alpha(1f)
286 }.start()
287 }
288
289 private fun hideView(view: View) {
290 if (view.visibility == View.INVISIBLE) {
291 return
292 }
293
294 view.apply {
295 alpha = 1f
296 isClickable = false
297 }.animate().apply {
298 duration = 300
299 alpha(0f)
300 }.withEndAction {
301 view.visibility = View.INVISIBLE
302 }
303 }
304
305 fun pageForward() {
306 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
307 }
308
309 fun pageBackward() {
310 binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1
311 }
312
313 fun setPageWarned(page: Int) {
314 hasBeenWarned[page] = true
315 }
316
317 private fun setInsets() =
318 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
319 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
320 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
321 view.setPadding(
322 barInsets.left + cutoutInsets.left,
323 barInsets.top + cutoutInsets.top,
324 barInsets.right + cutoutInsets.right,
325 barInsets.bottom + cutoutInsets.bottom
326 )
327 windowInsets
328 }
329}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupWarningDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupWarningDialogFragment.kt
new file mode 100644
index 000000000..b2c1d54af
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupWarningDialogFragment.kt
@@ -0,0 +1,86 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.content.Intent
9import android.net.Uri
10import android.os.Bundle
11import androidx.fragment.app.DialogFragment
12import com.google.android.material.dialog.MaterialAlertDialogBuilder
13import org.yuzu.yuzu_emu.R
14
15class SetupWarningDialogFragment : DialogFragment() {
16 private var titleId: Int = 0
17 private var descriptionId: Int = 0
18 private var helpLinkId: Int = 0
19 private var page: Int = 0
20
21 private lateinit var setupFragment: SetupFragment
22
23 override fun onCreate(savedInstanceState: Bundle?) {
24 super.onCreate(savedInstanceState)
25 titleId = requireArguments().getInt(TITLE)
26 descriptionId = requireArguments().getInt(DESCRIPTION)
27 helpLinkId = requireArguments().getInt(HELP_LINK)
28 page = requireArguments().getInt(PAGE)
29
30 setupFragment = requireParentFragment() as SetupFragment
31 }
32
33 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
34 val builder = MaterialAlertDialogBuilder(requireContext())
35 .setPositiveButton(R.string.warning_skip) { _: DialogInterface?, _: Int ->
36 setupFragment.pageForward()
37 setupFragment.setPageWarned(page)
38 }
39 .setNegativeButton(R.string.warning_cancel, null)
40
41 if (titleId != 0) {
42 builder.setTitle(titleId)
43 } else {
44 builder.setTitle("")
45 }
46 if (descriptionId != 0) {
47 builder.setMessage(descriptionId)
48 }
49 if (helpLinkId != 0) {
50 builder.setNeutralButton(R.string.warning_help) { _: DialogInterface?, _: Int ->
51 val helpLink = resources.getString(R.string.install_prod_keys_warning_help)
52 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(helpLink))
53 startActivity(intent)
54 }
55 }
56
57 return builder.show()
58 }
59
60 companion object {
61 const val TAG = "SetupWarningDialogFragment"
62
63 private const val TITLE = "Title"
64 private const val DESCRIPTION = "Description"
65 private const val HELP_LINK = "HelpLink"
66 private const val PAGE = "Page"
67
68 fun newInstance(
69 titleId: Int,
70 descriptionId: Int,
71 helpLinkId: Int,
72 page: Int
73 ): SetupWarningDialogFragment {
74 val dialog = SetupWarningDialogFragment()
75 val bundle = Bundle()
76 bundle.apply {
77 putInt(TITLE, titleId)
78 putInt(DESCRIPTION, descriptionId)
79 putInt(HELP_LINK, helpLinkId)
80 putInt(PAGE, page)
81 }
82 dialog.arguments = bundle
83 return dialog
84 }
85 }
86}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt
new file mode 100644
index 000000000..be5e4c86c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.layout
5
6import android.content.Context
7import androidx.recyclerview.widget.GridLayoutManager
8import androidx.recyclerview.widget.RecyclerView
9import androidx.recyclerview.widget.RecyclerView.Recycler
10import org.yuzu.yuzu_emu.R
11
12/**
13 * Cut down version of the solution provided here
14 * https://stackoverflow.com/questions/26666143/recyclerview-gridlayoutmanager-how-to-auto-detect-span-count
15 */
16class AutofitGridLayoutManager(
17 context: Context,
18 columnWidth: Int
19) : GridLayoutManager(context, 1) {
20 private var columnWidth = 0
21 private var isColumnWidthChanged = true
22 private var lastWidth = 0
23 private var lastHeight = 0
24
25 init {
26 setColumnWidth(checkedColumnWidth(context, columnWidth))
27 }
28
29 private fun checkedColumnWidth(context: Context, columnWidth: Int): Int {
30 var newColumnWidth = columnWidth
31 if (newColumnWidth <= 0) {
32 newColumnWidth = context.resources.getDimensionPixelSize(R.dimen.spacing_xtralarge)
33 }
34 return newColumnWidth
35 }
36
37 private fun setColumnWidth(newColumnWidth: Int) {
38 if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
39 columnWidth = newColumnWidth
40 isColumnWidthChanged = true
41 }
42 }
43
44 override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) {
45 val width = width
46 val height = height
47 if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
48 val totalSpace: Int = if (orientation == VERTICAL) {
49 width - paddingRight - paddingLeft
50 } else {
51 height - paddingTop - paddingBottom
52 }
53 val spanCount = 1.coerceAtLeast(totalSpace / columnWidth)
54 setSpanCount(spanCount)
55 isColumnWidthChanged = false
56 }
57 lastWidth = width
58 lastHeight = height
59 super.onLayoutChildren(recycler, state)
60 }
61}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
new file mode 100644
index 000000000..35d8000c5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import android.os.Parcelable
7import kotlinx.parcelize.Parcelize
8import kotlinx.serialization.Serializable
9import java.util.HashSet
10
11@Parcelize
12@Serializable
13class Game(
14 val title: String,
15 val description: String,
16 val regions: String,
17 val path: String,
18 val gameId: String,
19 val company: String,
20 val isHomebrew: Boolean
21) : Parcelable {
22 val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
23 val keyLastPlayedTime get() = "${gameId}_LastPlayed"
24
25 override fun equals(other: Any?): Boolean {
26 if (other !is Game)
27 return false
28
29 return hashCode() == other.hashCode()
30 }
31
32 override fun hashCode(): Int {
33 var result = title.hashCode()
34 result = 31 * result + description.hashCode()
35 result = 31 * result + regions.hashCode()
36 result = 31 * result + path.hashCode()
37 result = 31 * result + gameId.hashCode()
38 result = 31 * result + company.hashCode()
39 result = 31 * result + isHomebrew.hashCode()
40 return result
41 }
42
43 companion object {
44 val extensions: Set<String> = HashSet(
45 listOf(".xci", ".nsp", ".nca", ".nro")
46 )
47 }
48}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
new file mode 100644
index 000000000..d9b301210
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -0,0 +1,118 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import android.net.Uri
7import androidx.documentfile.provider.DocumentFile
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel
11import androidx.lifecycle.viewModelScope
12import androidx.preference.PreferenceManager
13import kotlinx.coroutines.Dispatchers
14import kotlinx.coroutines.launch
15import kotlinx.coroutines.withContext
16import kotlinx.serialization.ExperimentalSerializationApi
17import kotlinx.serialization.MissingFieldException
18import kotlinx.serialization.decodeFromString
19import kotlinx.serialization.json.Json
20import org.yuzu.yuzu_emu.NativeLibrary
21import org.yuzu.yuzu_emu.YuzuApplication
22import org.yuzu.yuzu_emu.utils.GameHelper
23import java.util.Locale
24
25@OptIn(ExperimentalSerializationApi::class)
26class GamesViewModel : ViewModel() {
27 private val _games = MutableLiveData<List<Game>>(emptyList())
28 val games: LiveData<List<Game>> get() = _games
29
30 private val _searchedGames = MutableLiveData<List<Game>>(emptyList())
31 val searchedGames: LiveData<List<Game>> get() = _searchedGames
32
33 private val _isReloading = MutableLiveData(false)
34 val isReloading: LiveData<Boolean> get() = _isReloading
35
36 private val _shouldSwapData = MutableLiveData(false)
37 val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData
38
39 private val _shouldScrollToTop = MutableLiveData(false)
40 val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop
41
42 private val _searchFocused = MutableLiveData(false)
43 val searchFocused: LiveData<Boolean> get() = _searchFocused
44
45 init {
46 // Ensure keys are loaded so that ROM metadata can be decrypted.
47 NativeLibrary.reloadKeys()
48
49 // Retrieve list of cached games
50 val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
51 .getStringSet(GameHelper.KEY_GAMES, emptySet())
52 if (storedGames!!.isNotEmpty()) {
53 val deserializedGames = mutableSetOf<Game>()
54 storedGames.forEach {
55 val game: Game
56 try {
57 game = Json.decodeFromString(it)
58 } catch (e: MissingFieldException) {
59 return@forEach
60 }
61
62 val gameExists =
63 DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
64 ?.exists()
65 if (gameExists == true) {
66 deserializedGames.add(game)
67 }
68 }
69 setGames(deserializedGames.toList())
70 }
71 reloadGames(false)
72 }
73
74 fun setGames(games: List<Game>) {
75 val sortedList = games.sortedWith(
76 compareBy(
77 { it.title.lowercase(Locale.getDefault()) },
78 { it.path }
79 )
80 )
81
82 _games.postValue(sortedList)
83 }
84
85 fun setSearchedGames(games: List<Game>) {
86 _searchedGames.postValue(games)
87 }
88
89 fun setShouldSwapData(shouldSwap: Boolean) {
90 _shouldSwapData.postValue(shouldSwap)
91 }
92
93 fun setShouldScrollToTop(shouldScroll: Boolean) {
94 _shouldScrollToTop.postValue(shouldScroll)
95 }
96
97 fun setSearchFocused(searchFocused: Boolean) {
98 _searchFocused.postValue(searchFocused)
99 }
100
101 fun reloadGames(directoryChanged: Boolean) {
102 if (isReloading.value == true)
103 return
104 _isReloading.postValue(true)
105
106 viewModelScope.launch {
107 withContext(Dispatchers.IO) {
108 NativeLibrary.resetRomMetadata()
109 setGames(GameHelper.getGames())
110 _isReloading.postValue(false)
111
112 if (directoryChanged) {
113 setShouldSwapData(true)
114 }
115 }
116 }
117 }
118}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
new file mode 100644
index 000000000..7049f2fa5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
@@ -0,0 +1,11 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6data class HomeSetting(
7 val titleId: Int,
8 val descriptionId: Int,
9 val iconId: Int,
10 val onClick: () -> Unit
11)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
new file mode 100644
index 000000000..263ee7144
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel
9
10class HomeViewModel : ViewModel() {
11 private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
12 val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible
13
14 private val _statusBarShadeVisible = MutableLiveData(true)
15 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
16
17 var navigatedToSetup = false
18
19 init {
20 _navigationVisible.value = Pair(false, false)
21 }
22
23 fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
24 if (_navigationVisible.value?.first == visible) {
25 return
26 }
27 _navigationVisible.value = Pair(visible, animated)
28 }
29
30 fun setStatusBarShadeVisibility(visible: Boolean) {
31 if (_statusBarShadeVisible.value == visible) {
32 return
33 }
34 _statusBarShadeVisible.value = visible
35 }
36}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/License.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/License.kt
new file mode 100644
index 000000000..f24d5cf34
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/License.kt
@@ -0,0 +1,16 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import android.os.Parcelable
7import kotlinx.parcelize.Parcelize
8
9@Parcelize
10data class License(
11 val titleId: Int,
12 val descriptionId: Int,
13 val linkId: Int,
14 val copyrightId: Int,
15 val licenseId: Int
16) : Parcelable
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MinimalDocumentFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MinimalDocumentFile.kt
new file mode 100644
index 000000000..b4b78e42d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MinimalDocumentFile.kt
@@ -0,0 +1,11 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import android.net.Uri
7import android.provider.DocumentsContract
8
9class MinimalDocumentFile(val filename: String, mimeType: String, val uri: Uri) {
10 val isDirectory: Boolean = mimeType == DocumentsContract.Document.MIME_TYPE_DIR
11}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
new file mode 100644
index 000000000..a0c878e1c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
@@ -0,0 +1,19 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6data class SetupPage(
7 val iconId: Int,
8 val titleId: Int,
9 val descriptionId: Int,
10 val buttonIconId: Int,
11 val leftAlignedIcon: Boolean,
12 val buttonTextId: Int,
13 val buttonAction: () -> Unit,
14 val hasWarning: Boolean,
15 val warningTitleId: Int = 0,
16 val warningDescriptionId: Int = 0,
17 val warningHelpLinkId: Int = 0,
18 val taskCompleted: () -> Boolean = { true }
19)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
new file mode 100644
index 000000000..27ea725a5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel
9import androidx.lifecycle.viewModelScope
10import kotlinx.coroutines.Dispatchers
11import kotlinx.coroutines.launch
12
13class TaskViewModel : ViewModel() {
14 private val _result = MutableLiveData<Any>()
15 val result: LiveData<Any> = _result
16
17 private val _isComplete = MutableLiveData<Boolean>()
18 val isComplete: LiveData<Boolean> = _isComplete
19
20 private val _isRunning = MutableLiveData<Boolean>()
21 val isRunning: LiveData<Boolean> = _isRunning
22
23 lateinit var task: () -> Any
24
25 init {
26 clear()
27 }
28
29 fun clear() {
30 _result.value = Any()
31 _isComplete.value = false
32 _isRunning.value = false
33 }
34
35 fun runTask() {
36 if (_isRunning.value == true) {
37 return
38 }
39 _isRunning.value = true
40
41 viewModelScope.launch(Dispatchers.IO) {
42 val res = task()
43 _result.postValue(res)
44 _isComplete.postValue(true)
45 }
46 }
47}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
new file mode 100644
index 000000000..aa424c768
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -0,0 +1,1066 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.overlay
5
6import android.app.Activity
7import android.content.Context
8import android.content.SharedPreferences
9import android.content.res.Configuration
10import android.graphics.Bitmap
11import android.graphics.Canvas
12import android.graphics.Point
13import android.graphics.Rect
14import android.graphics.drawable.Drawable
15import android.graphics.drawable.VectorDrawable
16import android.os.Build
17import android.util.AttributeSet
18import android.view.HapticFeedbackConstants
19import android.view.MotionEvent
20import android.view.SurfaceView
21import android.view.View
22import android.view.View.OnTouchListener
23import android.view.WindowInsets
24import androidx.core.content.ContextCompat
25import androidx.preference.PreferenceManager
26import androidx.window.layout.WindowMetricsCalculator
27import org.yuzu.yuzu_emu.NativeLibrary
28import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
29import org.yuzu.yuzu_emu.NativeLibrary.StickType
30import org.yuzu.yuzu_emu.R
31import org.yuzu.yuzu_emu.YuzuApplication
32import org.yuzu.yuzu_emu.features.settings.model.Settings
33import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
34import kotlin.math.max
35import kotlin.math.min
36
37/**
38 * Draws the interactive input overlay on top of the
39 * [SurfaceView] that is rendering emulation.
40 */
41class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs),
42 OnTouchListener {
43 private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
44 private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
45 private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet()
46
47 private var inEditMode = false
48 private var buttonBeingConfigured: InputOverlayDrawableButton? = null
49 private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
50 private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
51
52 private lateinit var windowInsets: WindowInsets
53
54 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
55 super.onLayout(changed, left, top, right, bottom)
56
57 windowInsets = rootWindowInsets
58
59 if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
60 defaultOverlay()
61 }
62
63 // Load the controls.
64 refreshControls()
65
66 // Set the on touch listener.
67 setOnTouchListener(this)
68
69 // Force draw
70 setWillNotDraw(false)
71
72 // Request focus for the overlay so it has priority on presses.
73 requestFocus()
74 }
75
76 override fun draw(canvas: Canvas) {
77 super.draw(canvas)
78 for (button in overlayButtons) {
79 button.draw(canvas)
80 }
81 for (dpad in overlayDpads) {
82 dpad.draw(canvas)
83 }
84 for (joystick in overlayJoysticks) {
85 joystick.draw(canvas)
86 }
87 }
88
89 override fun onTouch(v: View, event: MotionEvent): Boolean {
90 if (inEditMode) {
91 return onTouchWhileEditing(event)
92 }
93
94 var shouldUpdateView = false
95 val playerIndex =
96 if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
97
98 for (button in overlayButtons) {
99 if (!button.updateStatus(event)) {
100 continue
101 }
102 NativeLibrary.onGamePadButtonEvent(
103 playerIndex,
104 button.buttonId,
105 button.status
106 )
107 playHaptics(event)
108 shouldUpdateView = true
109 }
110
111 for (dpad in overlayDpads) {
112 if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide)) {
113 continue
114 }
115 NativeLibrary.onGamePadButtonEvent(
116 playerIndex,
117 dpad.upId,
118 dpad.upStatus
119 )
120 NativeLibrary.onGamePadButtonEvent(
121 playerIndex,
122 dpad.downId,
123 dpad.downStatus
124 )
125 NativeLibrary.onGamePadButtonEvent(
126 playerIndex,
127 dpad.leftId,
128 dpad.leftStatus
129 )
130 NativeLibrary.onGamePadButtonEvent(
131 playerIndex,
132 dpad.rightId,
133 dpad.rightStatus
134 )
135 playHaptics(event)
136 shouldUpdateView = true
137 }
138
139 for (joystick in overlayJoysticks) {
140 if (!joystick.updateStatus(event)) {
141 continue
142 }
143 val axisID = joystick.joystickId
144 NativeLibrary.onGamePadJoystickEvent(
145 playerIndex,
146 axisID,
147 joystick.xAxis,
148 joystick.realYAxis
149 )
150 NativeLibrary.onGamePadButtonEvent(
151 playerIndex,
152 joystick.buttonId,
153 joystick.buttonStatus
154 )
155 playHaptics(event)
156 shouldUpdateView = true
157 }
158
159 if (shouldUpdateView)
160 invalidate()
161
162 if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) {
163 return true
164 }
165
166 val pointerIndex = event.actionIndex
167 val xPosition = event.getX(pointerIndex).toInt()
168 val yPosition = event.getY(pointerIndex).toInt()
169 val pointerId = event.getPointerId(pointerIndex)
170 val motionEvent = event.action and MotionEvent.ACTION_MASK
171 val isActionDown =
172 motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
173 val isActionMove = motionEvent == MotionEvent.ACTION_MOVE
174 val isActionUp =
175 motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
176
177 if (isActionDown && !isTouchInputConsumed(pointerId)) {
178 NativeLibrary.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat())
179 }
180
181 if (isActionMove) {
182 for (i in 0 until event.pointerCount) {
183 val fingerId = event.getPointerId(i)
184 if (isTouchInputConsumed(fingerId)) {
185 continue
186 }
187 NativeLibrary.onTouchMoved(fingerId, event.getX(i), event.getY(i))
188 }
189 }
190
191 if (isActionUp && !isTouchInputConsumed(pointerId)) {
192 NativeLibrary.onTouchReleased(pointerId)
193 }
194
195 return true
196 }
197
198 private fun playHaptics(event: MotionEvent) {
199 if (EmulationMenuSettings.hapticFeedback) {
200 when (event.actionMasked) {
201 MotionEvent.ACTION_DOWN,
202 MotionEvent.ACTION_POINTER_DOWN ->
203 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
204
205 MotionEvent.ACTION_UP,
206 MotionEvent.ACTION_POINTER_UP ->
207 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE)
208 }
209 }
210 }
211
212 private fun isTouchInputConsumed(track_id: Int): Boolean {
213 for (button in overlayButtons) {
214 if (button.trackId == track_id) {
215 return true
216 }
217 }
218 for (dpad in overlayDpads) {
219 if (dpad.trackId == track_id) {
220 return true
221 }
222 }
223 for (joystick in overlayJoysticks) {
224 if (joystick.trackId == track_id) {
225 return true
226 }
227 }
228 return false
229 }
230
231 private fun onTouchWhileEditing(event: MotionEvent): Boolean {
232 val pointerIndex = event.actionIndex
233 val fingerPositionX = event.getX(pointerIndex).toInt()
234 val fingerPositionY = event.getY(pointerIndex).toInt()
235
236 // TODO: Provide support for portrait layout
237 //val orientation =
238 // if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
239
240 for (button in overlayButtons) {
241 // Determine the button state to apply based on the MotionEvent action flag.
242 when (event.action and MotionEvent.ACTION_MASK) {
243 MotionEvent.ACTION_DOWN,
244 MotionEvent.ACTION_POINTER_DOWN ->
245 // If no button is being moved now, remember the currently touched button to move.
246 if (buttonBeingConfigured == null &&
247 button.bounds.contains(
248 fingerPositionX,
249 fingerPositionY
250 )
251 ) {
252 buttonBeingConfigured = button
253 buttonBeingConfigured!!.onConfigureTouch(event)
254 }
255
256 MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
257 buttonBeingConfigured!!.onConfigureTouch(event)
258 invalidate()
259 return true
260 }
261
262 MotionEvent.ACTION_UP,
263 MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
264 // Persist button position by saving new place.
265 saveControlPosition(
266 buttonBeingConfigured!!.buttonId,
267 buttonBeingConfigured!!.bounds.centerX(),
268 buttonBeingConfigured!!.bounds.centerY(),
269 ""
270 )
271 buttonBeingConfigured = null
272 }
273 }
274 }
275
276 for (dpad in overlayDpads) {
277 // Determine the button state to apply based on the MotionEvent action flag.
278 when (event.action and MotionEvent.ACTION_MASK) {
279 MotionEvent.ACTION_DOWN,
280 MotionEvent.ACTION_POINTER_DOWN ->
281 // If no button is being moved now, remember the currently touched button to move.
282 if (buttonBeingConfigured == null &&
283 dpad.bounds.contains(fingerPositionX, fingerPositionY)
284 ) {
285 dpadBeingConfigured = dpad
286 dpadBeingConfigured!!.onConfigureTouch(event)
287 }
288
289 MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
290 dpadBeingConfigured!!.onConfigureTouch(event)
291 invalidate()
292 return true
293 }
294
295 MotionEvent.ACTION_UP,
296 MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
297 // Persist button position by saving new place.
298 saveControlPosition(
299 dpadBeingConfigured!!.upId,
300 dpadBeingConfigured!!.bounds.centerX(),
301 dpadBeingConfigured!!.bounds.centerY(),
302 ""
303 )
304 dpadBeingConfigured = null
305 }
306 }
307 }
308
309 for (joystick in overlayJoysticks) {
310 when (event.action) {
311 MotionEvent.ACTION_DOWN,
312 MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
313 joystick.bounds.contains(
314 fingerPositionX,
315 fingerPositionY
316 )
317 ) {
318 joystickBeingConfigured = joystick
319 joystickBeingConfigured!!.onConfigureTouch(event)
320 }
321
322 MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
323 joystickBeingConfigured!!.onConfigureTouch(event)
324 invalidate()
325 }
326
327 MotionEvent.ACTION_UP,
328 MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
329 saveControlPosition(
330 joystickBeingConfigured!!.buttonId,
331 joystickBeingConfigured!!.bounds.centerX(),
332 joystickBeingConfigured!!.bounds.centerY(),
333 ""
334 )
335 joystickBeingConfigured = null
336 }
337 }
338 }
339
340 return true
341 }
342
343 private fun addOverlayControls(orientation: String) {
344 val windowSize = getSafeScreenSize(context)
345 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) {
346 overlayButtons.add(
347 initializeOverlayButton(
348 context,
349 windowSize,
350 R.drawable.facebutton_a,
351 R.drawable.facebutton_a_depressed,
352 ButtonType.BUTTON_A,
353 orientation
354 )
355 )
356 }
357 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_1, true)) {
358 overlayButtons.add(
359 initializeOverlayButton(
360 context,
361 windowSize,
362 R.drawable.facebutton_b,
363 R.drawable.facebutton_b_depressed,
364 ButtonType.BUTTON_B,
365 orientation
366 )
367 )
368 }
369 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_2, true)) {
370 overlayButtons.add(
371 initializeOverlayButton(
372 context,
373 windowSize,
374 R.drawable.facebutton_x,
375 R.drawable.facebutton_x_depressed,
376 ButtonType.BUTTON_X,
377 orientation
378 )
379 )
380 }
381 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_3, true)) {
382 overlayButtons.add(
383 initializeOverlayButton(
384 context,
385 windowSize,
386 R.drawable.facebutton_y,
387 R.drawable.facebutton_y_depressed,
388 ButtonType.BUTTON_Y,
389 orientation
390 )
391 )
392 }
393 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_4, true)) {
394 overlayButtons.add(
395 initializeOverlayButton(
396 context,
397 windowSize,
398 R.drawable.l_shoulder,
399 R.drawable.l_shoulder_depressed,
400 ButtonType.TRIGGER_L,
401 orientation
402 )
403 )
404 }
405 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_5, true)) {
406 overlayButtons.add(
407 initializeOverlayButton(
408 context,
409 windowSize,
410 R.drawable.r_shoulder,
411 R.drawable.r_shoulder_depressed,
412 ButtonType.TRIGGER_R,
413 orientation
414 )
415 )
416 }
417 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_6, true)) {
418 overlayButtons.add(
419 initializeOverlayButton(
420 context,
421 windowSize,
422 R.drawable.zl_trigger,
423 R.drawable.zl_trigger_depressed,
424 ButtonType.TRIGGER_ZL,
425 orientation
426 )
427 )
428 }
429 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_7, true)) {
430 overlayButtons.add(
431 initializeOverlayButton(
432 context,
433 windowSize,
434 R.drawable.zr_trigger,
435 R.drawable.zr_trigger_depressed,
436 ButtonType.TRIGGER_ZR,
437 orientation
438 )
439 )
440 }
441 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_8, true)) {
442 overlayButtons.add(
443 initializeOverlayButton(
444 context,
445 windowSize,
446 R.drawable.facebutton_plus,
447 R.drawable.facebutton_plus_depressed,
448 ButtonType.BUTTON_PLUS,
449 orientation
450 )
451 )
452 }
453 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_9, true)) {
454 overlayButtons.add(
455 initializeOverlayButton(
456 context,
457 windowSize,
458 R.drawable.facebutton_minus,
459 R.drawable.facebutton_minus_depressed,
460 ButtonType.BUTTON_MINUS,
461 orientation
462 )
463 )
464 }
465 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_10, true)) {
466 overlayDpads.add(
467 initializeOverlayDpad(
468 context,
469 windowSize,
470 R.drawable.dpad_standard,
471 R.drawable.dpad_standard_cardinal_depressed,
472 R.drawable.dpad_standard_diagonal_depressed,
473 orientation
474 )
475 )
476 }
477 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_11, true)) {
478 overlayJoysticks.add(
479 initializeOverlayJoystick(
480 context,
481 windowSize,
482 R.drawable.joystick_range,
483 R.drawable.joystick,
484 R.drawable.joystick_depressed,
485 StickType.STICK_L,
486 ButtonType.STICK_L,
487 orientation
488 )
489 )
490 }
491 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_12, true)) {
492 overlayJoysticks.add(
493 initializeOverlayJoystick(
494 context,
495 windowSize,
496 R.drawable.joystick_range,
497 R.drawable.joystick,
498 R.drawable.joystick_depressed,
499 StickType.STICK_R,
500 ButtonType.STICK_R,
501 orientation
502 )
503 )
504 }
505 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_13, false)) {
506 overlayButtons.add(
507 initializeOverlayButton(
508 context,
509 windowSize,
510 R.drawable.facebutton_home,
511 R.drawable.facebutton_home_depressed,
512 ButtonType.BUTTON_HOME,
513 orientation
514 )
515 )
516 }
517 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_14, false)) {
518 overlayButtons.add(
519 initializeOverlayButton(
520 context,
521 windowSize,
522 R.drawable.facebutton_screenshot,
523 R.drawable.facebutton_screenshot_depressed,
524 ButtonType.BUTTON_CAPTURE,
525 orientation
526 )
527 )
528 }
529 }
530
531 fun refreshControls() {
532 // Remove all the overlay buttons from the HashSet.
533 overlayButtons.clear()
534 overlayDpads.clear()
535 overlayJoysticks.clear()
536 val orientation =
537 if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
538
539 // Add all the enabled overlay items back to the HashSet.
540 if (EmulationMenuSettings.showOverlay) {
541 addOverlayControls(orientation)
542 }
543 invalidate()
544 }
545
546 private fun saveControlPosition(sharedPrefsId: Int, x: Int, y: Int, orientation: String) {
547 val windowSize = getSafeScreenSize(context)
548 val min = windowSize.first
549 val max = windowSize.second
550 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
551 .putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x)
552 .putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y)
553 .apply()
554 }
555
556 fun setIsInEditMode(editMode: Boolean) {
557 inEditMode = editMode
558 }
559
560 private fun defaultOverlay() {
561 if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
562 defaultOverlayLandscape()
563 }
564
565 resetButtonPlacement()
566 preferences.edit()
567 .putBoolean(Settings.PREF_OVERLAY_INIT, true)
568 .apply()
569 }
570
571 fun resetButtonPlacement() {
572 defaultOverlayLandscape()
573 refreshControls()
574 }
575
576 private fun defaultOverlayLandscape() {
577 // Each value represents the position of the button in relation to the screen size without insets.
578 preferences.edit()
579 .putFloat(
580 ButtonType.BUTTON_A.toString() + "-X",
581 resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000
582 )
583 .putFloat(
584 ButtonType.BUTTON_A.toString() + "-Y",
585 resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000
586 )
587 .putFloat(
588 ButtonType.BUTTON_B.toString() + "-X",
589 resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000
590 )
591 .putFloat(
592 ButtonType.BUTTON_B.toString() + "-Y",
593 resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000
594 )
595 .putFloat(
596 ButtonType.BUTTON_X.toString() + "-X",
597 resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000
598 )
599 .putFloat(
600 ButtonType.BUTTON_X.toString() + "-Y",
601 resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000
602 )
603 .putFloat(
604 ButtonType.BUTTON_Y.toString() + "-X",
605 resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000
606 )
607 .putFloat(
608 ButtonType.BUTTON_Y.toString() + "-Y",
609 resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000
610 )
611 .putFloat(
612 ButtonType.TRIGGER_ZL.toString() + "-X",
613 resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000
614 )
615 .putFloat(
616 ButtonType.TRIGGER_ZL.toString() + "-Y",
617 resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000
618 )
619 .putFloat(
620 ButtonType.TRIGGER_ZR.toString() + "-X",
621 resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000
622 )
623 .putFloat(
624 ButtonType.TRIGGER_ZR.toString() + "-Y",
625 resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000
626 )
627 .putFloat(
628 ButtonType.DPAD_UP.toString() + "-X",
629 resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000
630 )
631 .putFloat(
632 ButtonType.DPAD_UP.toString() + "-Y",
633 resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000
634 )
635 .putFloat(
636 ButtonType.TRIGGER_L.toString() + "-X",
637 resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000
638 )
639 .putFloat(
640 ButtonType.TRIGGER_L.toString() + "-Y",
641 resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000
642 )
643 .putFloat(
644 ButtonType.TRIGGER_R.toString() + "-X",
645 resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000
646 )
647 .putFloat(
648 ButtonType.TRIGGER_R.toString() + "-Y",
649 resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000
650 )
651 .putFloat(
652 ButtonType.BUTTON_PLUS.toString() + "-X",
653 resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000
654 )
655 .putFloat(
656 ButtonType.BUTTON_PLUS.toString() + "-Y",
657 resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000
658 )
659 .putFloat(
660 ButtonType.BUTTON_MINUS.toString() + "-X",
661 resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000
662 )
663 .putFloat(
664 ButtonType.BUTTON_MINUS.toString() + "-Y",
665 resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000
666 )
667 .putFloat(
668 ButtonType.BUTTON_HOME.toString() + "-X",
669 resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000
670 )
671 .putFloat(
672 ButtonType.BUTTON_HOME.toString() + "-Y",
673 resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000
674 )
675 .putFloat(
676 ButtonType.BUTTON_CAPTURE.toString() + "-X",
677 resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X)
678 .toFloat() / 1000
679 )
680 .putFloat(
681 ButtonType.BUTTON_CAPTURE.toString() + "-Y",
682 resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y)
683 .toFloat() / 1000
684 )
685 .putFloat(
686 ButtonType.STICK_R.toString() + "-X",
687 resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000
688 )
689 .putFloat(
690 ButtonType.STICK_R.toString() + "-Y",
691 resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000
692 )
693 .putFloat(
694 ButtonType.STICK_L.toString() + "-X",
695 resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000
696 )
697 .putFloat(
698 ButtonType.STICK_L.toString() + "-Y",
699 resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000
700 )
701 .apply()
702 }
703
704 override fun isInEditMode(): Boolean {
705 return inEditMode
706 }
707
708 companion object {
709 private val preferences: SharedPreferences =
710 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
711
712 /**
713 * Resizes a [Bitmap] by a given scale factor
714 *
715 * @param context Context for getting the vector drawable
716 * @param drawableId The ID of the drawable to scale.
717 * @param scale The scale factor for the bitmap.
718 * @return The scaled [Bitmap]
719 */
720 private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap {
721 val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable
722
723 val bitmap = Bitmap.createBitmap(
724 (vectorDrawable.intrinsicWidth * scale).toInt(),
725 (vectorDrawable.intrinsicHeight * scale).toInt(),
726 Bitmap.Config.ARGB_8888
727 )
728
729 val dm = context.resources.displayMetrics
730 val minScreenDimension = min(dm.widthPixels, dm.heightPixels)
731
732 val maxBitmapDimension = max(bitmap.width, bitmap.height)
733 val bitmapScale = scale * minScreenDimension / maxBitmapDimension
734
735 val scaledBitmap = Bitmap.createScaledBitmap(
736 bitmap,
737 (bitmap.width * bitmapScale).toInt(),
738 (bitmap.height * bitmapScale).toInt(),
739 true
740 )
741
742 val canvas = Canvas(scaledBitmap)
743 vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
744 vectorDrawable.draw(canvas)
745 return scaledBitmap
746 }
747
748 /**
749 * Gets the safe screen size for drawing the overlay
750 *
751 * @param context Context for getting the window metrics
752 * @return A pair of points, the first being the top left corner of the safe area,
753 * the second being the bottom right corner of the safe area
754 */
755 private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
756 // Get screen size
757 val windowMetrics =
758 WindowMetricsCalculator.getOrCreate()
759 .computeCurrentWindowMetrics(context as Activity)
760 var maxY = windowMetrics.bounds.height().toFloat()
761 var maxX = windowMetrics.bounds.width().toFloat()
762 var minY = 0
763 var minX = 0
764
765 // If we have API access, calculate the safe area to draw the overlay
766 var cutoutLeft = 0
767 var cutoutBottom = 0
768 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
769 val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
770 if (insets != null) {
771 if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
772 insets.boundingRectTop.bottom.toFloat() else maxY
773 if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
774 insets.boundingRectRight.left.toFloat() else maxX
775
776 minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
777 minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
778
779 cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
780 cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
781 }
782 }
783
784 // This makes sure that if we have an inset on one side of the screen, we mirror it on
785 // the other side. Since removing space from one of the max values messes with the scale,
786 // we also have to account for it using our min values.
787 if (maxX.toInt() != windowMetrics.bounds.width()) minX += cutoutLeft
788 if (maxY.toInt() != windowMetrics.bounds.height()) minY += cutoutBottom
789 if (minX > 0 && maxX.toInt() == windowMetrics.bounds.width()) {
790 maxX -= (minX * 2)
791 } else if (minX > 0) {
792 maxX -= minX
793 }
794 if (minY > 0 && maxY.toInt() == windowMetrics.bounds.height()) {
795 maxY -= (minY * 2)
796 } else if (minY > 0) {
797 maxY -= minY
798 }
799
800 return Pair(Point(minX, minY), Point(maxX.toInt(), maxY.toInt()))
801 }
802
803 /**
804 * Initializes an InputOverlayDrawableButton, given by resId, with all of the
805 * parameters set for it to be properly shown on the InputOverlay.
806 *
807 *
808 * This works due to the way the X and Y coordinates are stored within
809 * the [SharedPreferences].
810 *
811 *
812 * In the input overlay configuration menu,
813 * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay).
814 * the X and Y coordinates of the button at the END of its touch event
815 * (when you remove your finger/stylus from the touchscreen) are then stored
816 * within a SharedPreferences instance so that those values can be retrieved here.
817 *
818 *
819 * This has a few benefits over the conventional way of storing the values
820 * (ie. within the yuzu ini file).
821 *
822 * * No native calls
823 * * Keeps Android-only values inside the Android environment
824 *
825 *
826 *
827 * Technically no modifications should need to be performed on the returned
828 * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait
829 * for Android to call the onDraw method.
830 *
831 * @param context The current [Context].
832 * @param windowSize The size of the window to draw the overlay on.
833 * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
834 * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
835 * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
836 * @return An [InputOverlayDrawableButton] with the correct drawing bounds set.
837 */
838 private fun initializeOverlayButton(
839 context: Context,
840 windowSize: Pair<Point, Point>,
841 defaultResId: Int,
842 pressedResId: Int,
843 buttonId: Int,
844 orientation: String
845 ): InputOverlayDrawableButton {
846 // Resources handle for fetching the initial Drawable resource.
847 val res = context.resources
848
849 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
850 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
851
852 // Decide scale based on button ID and user preference
853 var scale: Float = when (buttonId) {
854 ButtonType.BUTTON_HOME,
855 ButtonType.BUTTON_CAPTURE,
856 ButtonType.BUTTON_PLUS,
857 ButtonType.BUTTON_MINUS -> 0.07f
858
859 ButtonType.TRIGGER_L,
860 ButtonType.TRIGGER_R,
861 ButtonType.TRIGGER_ZL,
862 ButtonType.TRIGGER_ZR -> 0.26f
863
864 else -> 0.11f
865 }
866 scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
867 scale /= 100f
868
869 // Initialize the InputOverlayDrawableButton.
870 val defaultStateBitmap = getBitmap(context, defaultResId, scale)
871 val pressedStateBitmap = getBitmap(context, pressedResId, scale)
872 val overlayDrawable =
873 InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId)
874
875 // Get the minimum and maximum coordinates of the screen where the button can be placed.
876 val min = windowSize.first
877 val max = windowSize.second
878
879 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
880 // These were set in the input overlay configuration menu.
881 val xKey = "$buttonId$orientation-X"
882 val yKey = "$buttonId$orientation-Y"
883 val drawableXPercent = sPrefs.getFloat(xKey, 0f)
884 val drawableYPercent = sPrefs.getFloat(yKey, 0f)
885 val drawableX = (drawableXPercent * max.x + min.x).toInt()
886 val drawableY = (drawableYPercent * max.y + min.y).toInt()
887 val width = overlayDrawable.width
888 val height = overlayDrawable.height
889
890 // Now set the bounds for the InputOverlayDrawableButton.
891 // This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be.
892 overlayDrawable.setBounds(
893 drawableX - (width / 2),
894 drawableY - (height / 2),
895 drawableX + (width / 2),
896 drawableY + (height / 2)
897 )
898
899 // Need to set the image's position
900 overlayDrawable.setPosition(
901 drawableX - (width / 2),
902 drawableY - (height / 2)
903 )
904 val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
905 overlayDrawable.setOpacity(savedOpacity * 255 / 100)
906 return overlayDrawable
907 }
908
909 /**
910 * Initializes an [InputOverlayDrawableDpad]
911 *
912 * @param context The current [Context].
913 * @param windowSize The size of the window to draw the overlay on.
914 * @param defaultResId The [Bitmap] resource ID of the default state.
915 * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction.
916 * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions.
917 * @return the initialized [InputOverlayDrawableDpad]
918 */
919 private fun initializeOverlayDpad(
920 context: Context,
921 windowSize: Pair<Point, Point>,
922 defaultResId: Int,
923 pressedOneDirectionResId: Int,
924 pressedTwoDirectionsResId: Int,
925 orientation: String
926 ): InputOverlayDrawableDpad {
927 // Resources handle for fetching the initial Drawable resource.
928 val res = context.resources
929
930 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad.
931 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
932
933 // Decide scale based on button ID and user preference
934 var scale = 0.25f
935 scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
936 scale /= 100f
937
938 // Initialize the InputOverlayDrawableDpad.
939 val defaultStateBitmap =
940 getBitmap(context, defaultResId, scale)
941 val pressedOneDirectionStateBitmap = getBitmap(context, pressedOneDirectionResId, scale)
942 val pressedTwoDirectionsStateBitmap =
943 getBitmap(context, pressedTwoDirectionsResId, scale)
944
945 val overlayDrawable = InputOverlayDrawableDpad(
946 res,
947 defaultStateBitmap,
948 pressedOneDirectionStateBitmap,
949 pressedTwoDirectionsStateBitmap,
950 ButtonType.DPAD_UP,
951 ButtonType.DPAD_DOWN,
952 ButtonType.DPAD_LEFT,
953 ButtonType.DPAD_RIGHT
954 )
955
956 // Get the minimum and maximum coordinates of the screen where the button can be placed.
957 val min = windowSize.first
958 val max = windowSize.second
959
960 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
961 // These were set in the input overlay configuration menu.
962 val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f)
963 val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f)
964 val drawableX = (drawableXPercent * max.x + min.x).toInt()
965 val drawableY = (drawableYPercent * max.y + min.y).toInt()
966 val width = overlayDrawable.width
967 val height = overlayDrawable.height
968
969 // Now set the bounds for the InputOverlayDrawableDpad.
970 // This will dictate where on the screen (and the what the size) the InputOverlayDrawableDpad will be.
971 overlayDrawable.setBounds(
972 drawableX - (width / 2),
973 drawableY - (height / 2),
974 drawableX + (width / 2),
975 drawableY + (height / 2)
976 )
977
978 // Need to set the image's position
979 overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2))
980 val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
981 overlayDrawable.setOpacity(savedOpacity * 255 / 100)
982 return overlayDrawable
983 }
984
985 /**
986 * Initializes an [InputOverlayDrawableJoystick]
987 *
988 * @param context The current [Context]
989 * @param windowSize The size of the window to draw the overlay on.
990 * @param resOuter Resource ID for the outer image of the joystick (the static image that shows the circular bounds).
991 * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around).
992 * @param pressedResInner Resource ID for the pressed inner image of the joystick.
993 * @param joystick Identifier for which joystick this is.
994 * @param button Identifier for which joystick button this is.
995 * @return the initialized [InputOverlayDrawableJoystick].
996 */
997 private fun initializeOverlayJoystick(
998 context: Context,
999 windowSize: Pair<Point, Point>,
1000 resOuter: Int,
1001 defaultResInner: Int,
1002 pressedResInner: Int,
1003 joystick: Int,
1004 button: Int,
1005 orientation: String
1006 ): InputOverlayDrawableJoystick {
1007 // Resources handle for fetching the initial Drawable resource.
1008 val res = context.resources
1009
1010 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
1011 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
1012
1013 // Decide scale based on user preference
1014 var scale = 0.3f
1015 scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
1016 scale /= 100f
1017
1018 // Initialize the InputOverlayDrawableJoystick.
1019 val bitmapOuter = getBitmap(context, resOuter, scale)
1020 val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
1021 val bitmapInnerPressed = getBitmap(context, pressedResInner, 1.0f)
1022
1023 // Get the minimum and maximum coordinates of the screen where the button can be placed.
1024 val min = windowSize.first
1025 val max = windowSize.second
1026
1027 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
1028 // These were set in the input overlay configuration menu.
1029 val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f)
1030 val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f)
1031 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1032 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1033 val outerScale = 1.66f
1034
1035 // Now set the bounds for the InputOverlayDrawableJoystick.
1036 // This will dictate where on the screen (and the what the size) the InputOverlayDrawableJoystick will be.
1037 val outerSize = bitmapOuter.width
1038 val outerRect = Rect(
1039 drawableX - (outerSize / 2),
1040 drawableY - (outerSize / 2),
1041 drawableX + (outerSize / 2),
1042 drawableY + (outerSize / 2)
1043 )
1044 val innerRect =
1045 Rect(0, 0, (outerSize / outerScale).toInt(), (outerSize / outerScale).toInt())
1046
1047 // Send the drawableId to the joystick so it can be referenced when saving control position.
1048 val overlayDrawable = InputOverlayDrawableJoystick(
1049 res,
1050 bitmapOuter,
1051 bitmapInnerDefault,
1052 bitmapInnerPressed,
1053 outerRect,
1054 innerRect,
1055 joystick,
1056 button
1057 )
1058
1059 // Need to set the image's position
1060 overlayDrawable.setPosition(drawableX, drawableY)
1061 val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
1062 overlayDrawable.setOpacity(savedOpacity * 255 / 100)
1063 return overlayDrawable
1064 }
1065 }
1066}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
new file mode 100644
index 000000000..4a93e0b14
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
@@ -0,0 +1,148 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.overlay
5
6import android.content.res.Resources
7import android.graphics.Bitmap
8import android.graphics.Canvas
9import android.graphics.Rect
10import android.graphics.drawable.BitmapDrawable
11import android.view.MotionEvent
12import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
13
14/**
15 * Custom [BitmapDrawable] that is capable
16 * of storing it's own ID.
17 *
18 * @param res [Resources] instance.
19 * @param defaultStateBitmap [Bitmap] to use with the default state Drawable.
20 * @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable.
21 * @param buttonId Identifier for this type of button.
22 */
23class InputOverlayDrawableButton(
24 res: Resources,
25 defaultStateBitmap: Bitmap,
26 pressedStateBitmap: Bitmap,
27 val buttonId: Int
28) {
29 // The ID value what motion event is tracking
30 var trackId: Int
31
32 // The drawable position on the screen
33 private var buttonPositionX = 0
34 private var buttonPositionY = 0
35
36 val width: Int
37 val height: Int
38
39 private val defaultStateBitmap: BitmapDrawable
40 private val pressedStateBitmap: BitmapDrawable
41 private var pressedState = false
42
43 private var previousTouchX = 0
44 private var previousTouchY = 0
45 var controlPositionX = 0
46 var controlPositionY = 0
47
48 init {
49 this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap)
50 this.pressedStateBitmap = BitmapDrawable(res, pressedStateBitmap)
51 trackId = -1
52 width = this.defaultStateBitmap.intrinsicWidth
53 height = this.defaultStateBitmap.intrinsicHeight
54 }
55
56 /**
57 * Updates button status based on the motion event.
58 *
59 * @return true if value was changed
60 */
61 fun updateStatus(event: MotionEvent): Boolean {
62 val pointerIndex = event.actionIndex
63 val xPosition = event.getX(pointerIndex).toInt()
64 val yPosition = event.getY(pointerIndex).toInt()
65 val pointerId = event.getPointerId(pointerIndex)
66 val motionEvent = event.action and MotionEvent.ACTION_MASK
67 val isActionDown =
68 motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
69 val isActionUp =
70 motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
71
72 if (isActionDown) {
73 if (!bounds.contains(xPosition, yPosition)) {
74 return false
75 }
76 pressedState = true
77 trackId = pointerId
78 return true
79 }
80
81 if (isActionUp) {
82 if (trackId != pointerId) {
83 return false
84 }
85 pressedState = false
86 trackId = -1
87 return true
88 }
89
90 return false
91 }
92
93 fun setPosition(x: Int, y: Int) {
94 buttonPositionX = x
95 buttonPositionY = y
96 }
97
98 fun draw(canvas: Canvas?) {
99 currentStateBitmapDrawable.draw(canvas!!)
100 }
101
102 private val currentStateBitmapDrawable: BitmapDrawable
103 get() = if (pressedState) pressedStateBitmap else defaultStateBitmap
104
105 fun onConfigureTouch(event: MotionEvent): Boolean {
106 val pointerIndex = event.actionIndex
107 val fingerPositionX = event.getX(pointerIndex).toInt()
108 val fingerPositionY = event.getY(pointerIndex).toInt()
109
110 when (event.action) {
111 MotionEvent.ACTION_DOWN -> {
112 previousTouchX = fingerPositionX
113 previousTouchY = fingerPositionY
114 controlPositionX = fingerPositionX - (width / 2)
115 controlPositionY = fingerPositionY - (height / 2)
116 }
117
118 MotionEvent.ACTION_MOVE -> {
119 controlPositionX += fingerPositionX - previousTouchX
120 controlPositionY += fingerPositionY - previousTouchY
121 setBounds(
122 controlPositionX,
123 controlPositionY,
124 width + controlPositionX,
125 height + controlPositionY
126 )
127 previousTouchX = fingerPositionX
128 previousTouchY = fingerPositionY
129 }
130 }
131 return true
132 }
133
134 fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
135 defaultStateBitmap.setBounds(left, top, right, bottom)
136 pressedStateBitmap.setBounds(left, top, right, bottom)
137 }
138
139 fun setOpacity(value: Int) {
140 defaultStateBitmap.alpha = value
141 pressedStateBitmap.alpha = value
142 }
143
144 val status: Int
145 get() = if (pressedState) ButtonState.PRESSED else ButtonState.RELEASED
146 val bounds: Rect
147 get() = defaultStateBitmap.bounds
148}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
new file mode 100644
index 000000000..43d664d21
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
@@ -0,0 +1,274 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.overlay
5
6import android.content.res.Resources
7import android.graphics.Bitmap
8import android.graphics.Canvas
9import android.graphics.Rect
10import android.graphics.drawable.BitmapDrawable
11import android.view.MotionEvent
12import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
13
14/**
15 * Custom [BitmapDrawable] that is capable
16 * of storing it's own ID.
17 *
18 * @param res [Resources] instance.
19 * @param defaultStateBitmap [Bitmap] of the default state.
20 * @param pressedOneDirectionStateBitmap [Bitmap] of the pressed state in one direction.
21 * @param pressedTwoDirectionsStateBitmap [Bitmap] of the pressed state in two direction.
22 * @param buttonUp Identifier for the up button.
23 * @param buttonDown Identifier for the down button.
24 * @param buttonLeft Identifier for the left button.
25 * @param buttonRight Identifier for the right button.
26 */
27class InputOverlayDrawableDpad(
28 res: Resources,
29 defaultStateBitmap: Bitmap,
30 pressedOneDirectionStateBitmap: Bitmap,
31 pressedTwoDirectionsStateBitmap: Bitmap,
32 buttonUp: Int,
33 buttonDown: Int,
34 buttonLeft: Int,
35 buttonRight: Int
36) {
37 /**
38 * Gets one of the InputOverlayDrawableDpad's button IDs.
39 *
40 * @return the requested InputOverlayDrawableDpad's button ID.
41 */
42 // The ID identifying what type of button this Drawable represents.
43 val upId: Int
44 val downId: Int
45 val leftId: Int
46 val rightId: Int
47 var trackId: Int
48
49 val width: Int
50 val height: Int
51
52 private val defaultStateBitmap: BitmapDrawable
53 private val pressedOneDirectionStateBitmap: BitmapDrawable
54 private val pressedTwoDirectionsStateBitmap: BitmapDrawable
55
56 private var previousTouchX = 0
57 private var previousTouchY = 0
58 private var controlPositionX = 0
59 private var controlPositionY = 0
60
61 private var upButtonState = false
62 private var downButtonState = false
63 private var leftButtonState = false
64 private var rightButtonState = false
65
66 init {
67 this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap)
68 this.pressedOneDirectionStateBitmap = BitmapDrawable(res, pressedOneDirectionStateBitmap)
69 this.pressedTwoDirectionsStateBitmap = BitmapDrawable(res, pressedTwoDirectionsStateBitmap)
70 width = this.defaultStateBitmap.intrinsicWidth
71 height = this.defaultStateBitmap.intrinsicHeight
72 upId = buttonUp
73 downId = buttonDown
74 leftId = buttonLeft
75 rightId = buttonRight
76 trackId = -1
77 }
78
79 fun updateStatus(event: MotionEvent, dpad_slide: Boolean): Boolean {
80 val pointerIndex = event.actionIndex
81 val xPosition = event.getX(pointerIndex).toInt()
82 val yPosition = event.getY(pointerIndex).toInt()
83 val pointerId = event.getPointerId(pointerIndex)
84 val motionEvent = event.action and MotionEvent.ACTION_MASK
85 val isActionDown =
86 motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
87 val isActionUp =
88 motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
89 if (isActionDown) {
90 if (!bounds.contains(xPosition, yPosition)) {
91 return false
92 }
93 trackId = pointerId
94 }
95 if (isActionUp) {
96 if (trackId != pointerId) {
97 return false
98 }
99 trackId = -1
100 upButtonState = false
101 downButtonState = false
102 leftButtonState = false
103 rightButtonState = false
104 return true
105 }
106 if (trackId == -1) {
107 return false
108 }
109 if (!dpad_slide && !isActionDown) {
110 return false
111 }
112 for (i in 0 until event.pointerCount) {
113 if (trackId != event.getPointerId(i)) {
114 continue
115 }
116
117 var touchX = event.getX(i)
118 var touchY = event.getY(i)
119 var maxY = bounds.bottom.toFloat()
120 var maxX = bounds.right.toFloat()
121 touchX -= bounds.centerX().toFloat()
122 maxX -= bounds.centerX().toFloat()
123 touchY -= bounds.centerY().toFloat()
124 maxY -= bounds.centerY().toFloat()
125 val axisX = touchX / maxX
126 val axisY = touchY / maxY
127 val oldUpState = upButtonState
128 val oldDownState = downButtonState
129 val oldLeftState = leftButtonState
130 val oldRightState = rightButtonState
131
132 upButtonState = axisY < -VIRT_AXIS_DEADZONE
133 downButtonState = axisY > VIRT_AXIS_DEADZONE
134 leftButtonState = axisX < -VIRT_AXIS_DEADZONE
135 rightButtonState = axisX > VIRT_AXIS_DEADZONE
136 return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState
137 }
138 return false
139 }
140
141 fun draw(canvas: Canvas) {
142 val px = controlPositionX + width / 2
143 val py = controlPositionY + height / 2
144
145 // Pressed up
146 if (upButtonState && !leftButtonState && !rightButtonState) {
147 pressedOneDirectionStateBitmap.draw(canvas)
148 return
149 }
150
151 // Pressed down
152 if (downButtonState && !leftButtonState && !rightButtonState) {
153 canvas.save()
154 canvas.rotate(180f, px.toFloat(), py.toFloat())
155 pressedOneDirectionStateBitmap.draw(canvas)
156 canvas.restore()
157 return
158 }
159
160 // Pressed left
161 if (leftButtonState && !upButtonState && !downButtonState) {
162 canvas.save()
163 canvas.rotate(270f, px.toFloat(), py.toFloat())
164 pressedOneDirectionStateBitmap.draw(canvas)
165 canvas.restore()
166 return
167 }
168
169 // Pressed right
170 if (rightButtonState && !upButtonState && !downButtonState) {
171 canvas.save()
172 canvas.rotate(90f, px.toFloat(), py.toFloat())
173 pressedOneDirectionStateBitmap.draw(canvas)
174 canvas.restore()
175 return
176 }
177
178 // Pressed up left
179 if (upButtonState && leftButtonState && !rightButtonState) {
180 pressedTwoDirectionsStateBitmap.draw(canvas)
181 return
182 }
183
184 // Pressed up right
185 if (upButtonState && !leftButtonState && rightButtonState) {
186 canvas.save()
187 canvas.rotate(90f, px.toFloat(), py.toFloat())
188 pressedTwoDirectionsStateBitmap.draw(canvas)
189 canvas.restore()
190 return
191 }
192
193 // Pressed down right
194 if (downButtonState && !leftButtonState && rightButtonState) {
195 canvas.save()
196 canvas.rotate(180f, px.toFloat(), py.toFloat())
197 pressedTwoDirectionsStateBitmap.draw(canvas)
198 canvas.restore()
199 return
200 }
201
202 // Pressed down left
203 if (downButtonState && leftButtonState && !rightButtonState) {
204 canvas.save()
205 canvas.rotate(270f, px.toFloat(), py.toFloat())
206 pressedTwoDirectionsStateBitmap.draw(canvas)
207 canvas.restore()
208 return
209 }
210
211 // Not pressed
212 defaultStateBitmap.draw(canvas)
213 }
214
215 val upStatus: Int
216 get() = if (upButtonState) ButtonState.PRESSED else ButtonState.RELEASED
217 val downStatus: Int
218 get() = if (downButtonState) ButtonState.PRESSED else ButtonState.RELEASED
219 val leftStatus: Int
220 get() = if (leftButtonState) ButtonState.PRESSED else ButtonState.RELEASED
221 val rightStatus: Int
222 get() = if (rightButtonState) ButtonState.PRESSED else ButtonState.RELEASED
223
224 fun onConfigureTouch(event: MotionEvent): Boolean {
225 val pointerIndex = event.actionIndex
226 val fingerPositionX = event.getX(pointerIndex).toInt()
227 val fingerPositionY = event.getY(pointerIndex).toInt()
228
229 when (event.action) {
230 MotionEvent.ACTION_DOWN -> {
231 previousTouchX = fingerPositionX
232 previousTouchY = fingerPositionY
233 }
234
235 MotionEvent.ACTION_MOVE -> {
236 controlPositionX += fingerPositionX - previousTouchX
237 controlPositionY += fingerPositionY - previousTouchY
238 setBounds(
239 controlPositionX,
240 controlPositionY,
241 width + controlPositionX,
242 height + controlPositionY
243 )
244 previousTouchX = fingerPositionX
245 previousTouchY = fingerPositionY
246 }
247 }
248 return true
249 }
250
251 fun setPosition(x: Int, y: Int) {
252 controlPositionX = x
253 controlPositionY = y
254 }
255
256 fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
257 defaultStateBitmap.setBounds(left, top, right, bottom)
258 pressedOneDirectionStateBitmap.setBounds(left, top, right, bottom)
259 pressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom)
260 }
261
262 fun setOpacity(value: Int) {
263 defaultStateBitmap.alpha = value
264 pressedOneDirectionStateBitmap.alpha = value
265 pressedTwoDirectionsStateBitmap.alpha = value
266 }
267
268 val bounds: Rect
269 get() = defaultStateBitmap.bounds
270
271 companion object {
272 const val VIRT_AXIS_DEADZONE = 0.5f
273 }
274}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
new file mode 100644
index 000000000..f1d32192a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -0,0 +1,282 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.overlay
5
6import android.content.res.Resources
7import android.graphics.Bitmap
8import android.graphics.Canvas
9import android.graphics.Rect
10import android.graphics.drawable.BitmapDrawable
11import android.view.MotionEvent
12import org.yuzu.yuzu_emu.NativeLibrary
13import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
14import kotlin.math.atan2
15import kotlin.math.cos
16import kotlin.math.sin
17import kotlin.math.sqrt
18
19/**
20 * Custom [BitmapDrawable] that is capable
21 * of storing it's own ID.
22 *
23 * @param res [Resources] instance.
24 * @param bitmapOuter [Bitmap] which represents the outer non-movable part of the joystick.
25 * @param bitmapInnerDefault [Bitmap] which represents the default inner movable part of the joystick.
26 * @param bitmapInnerPressed [Bitmap] which represents the pressed inner movable part of the joystick.
27 * @param rectOuter [Rect] which represents the outer joystick bounds.
28 * @param rectInner [Rect] which represents the inner joystick bounds.
29 * @param joystickId The ID value what type of joystick this Drawable represents.
30 * @param buttonId The ID value what type of button this Drawable represents.
31 */
32class InputOverlayDrawableJoystick(
33 res: Resources,
34 bitmapOuter: Bitmap,
35 bitmapInnerDefault: Bitmap,
36 bitmapInnerPressed: Bitmap,
37 rectOuter: Rect,
38 rectInner: Rect,
39 val joystickId: Int,
40 val buttonId: Int
41) {
42 // The ID value what motion event is tracking
43 var trackId = -1
44
45 var xAxis = 0f
46 private var yAxis = 0f
47
48 val width: Int
49 val height: Int
50
51 private var opacity: Int = 0
52
53 private var virtBounds: Rect
54 private var origBounds: Rect
55
56 private val outerBitmap: BitmapDrawable
57 private val defaultStateInnerBitmap: BitmapDrawable
58 private val pressedStateInnerBitmap: BitmapDrawable
59
60 private var previousTouchX = 0
61 private var previousTouchY = 0
62 var controlPositionX = 0
63 var controlPositionY = 0
64
65 private val boundsBoxBitmap: BitmapDrawable
66
67 private var pressedState = false
68
69 // TODO: Add button support
70 val buttonStatus: Int
71 get() =
72 NativeLibrary.ButtonState.RELEASED
73 var bounds: Rect
74 get() = outerBitmap.bounds
75 set(bounds) {
76 outerBitmap.bounds = bounds
77 }
78
79 // Nintendo joysticks have y axis inverted
80 val realYAxis: Float
81 get() = -yAxis
82
83 private val currentStateBitmapDrawable: BitmapDrawable
84 get() = if (pressedState) pressedStateInnerBitmap else defaultStateInnerBitmap
85
86 init {
87 outerBitmap = BitmapDrawable(res, bitmapOuter)
88 defaultStateInnerBitmap = BitmapDrawable(res, bitmapInnerDefault)
89 pressedStateInnerBitmap = BitmapDrawable(res, bitmapInnerPressed)
90 boundsBoxBitmap = BitmapDrawable(res, bitmapOuter)
91 width = bitmapOuter.width
92 height = bitmapOuter.height
93 bounds = rectOuter
94 defaultStateInnerBitmap.bounds = rectInner
95 pressedStateInnerBitmap.bounds = rectInner
96 virtBounds = bounds
97 origBounds = outerBitmap.copyBounds()
98 boundsBoxBitmap.alpha = 0
99 boundsBoxBitmap.bounds = virtBounds
100 setInnerBounds()
101 }
102
103 fun draw(canvas: Canvas?) {
104 outerBitmap.draw(canvas!!)
105 currentStateBitmapDrawable.draw(canvas)
106 boundsBoxBitmap.draw(canvas)
107 }
108
109 fun updateStatus(event: MotionEvent): Boolean {
110 val pointerIndex = event.actionIndex
111 val xPosition = event.getX(pointerIndex).toInt()
112 val yPosition = event.getY(pointerIndex).toInt()
113 val pointerId = event.getPointerId(pointerIndex)
114 val motionEvent = event.action and MotionEvent.ACTION_MASK
115 val isActionDown =
116 motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
117 val isActionUp =
118 motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
119
120 if (isActionDown) {
121 if (!bounds.contains(xPosition, yPosition)) {
122 return false
123 }
124 pressedState = true
125 outerBitmap.alpha = 0
126 boundsBoxBitmap.alpha = opacity
127 if (EmulationMenuSettings.joystickRelCenter) {
128 virtBounds.offset(
129 xPosition - virtBounds.centerX(),
130 yPosition - virtBounds.centerY()
131 )
132 }
133 boundsBoxBitmap.bounds = virtBounds
134 trackId = pointerId
135 }
136
137 if (isActionUp) {
138 if (trackId != pointerId) {
139 return false
140 }
141 pressedState = false
142 xAxis = 0.0f
143 yAxis = 0.0f
144 outerBitmap.alpha = opacity
145 boundsBoxBitmap.alpha = 0
146 virtBounds = Rect(
147 origBounds.left,
148 origBounds.top,
149 origBounds.right,
150 origBounds.bottom
151 )
152 bounds = Rect(
153 origBounds.left,
154 origBounds.top,
155 origBounds.right,
156 origBounds.bottom
157 )
158 setInnerBounds()
159 trackId = -1
160 return true
161 }
162
163 if (trackId == -1) return false
164
165 for (i in 0 until event.pointerCount) {
166 if (trackId != event.getPointerId(i)) {
167 continue
168 }
169 var touchX = event.getX(i)
170 var touchY = event.getY(i)
171 var maxY = virtBounds.bottom.toFloat()
172 var maxX = virtBounds.right.toFloat()
173 touchX -= virtBounds.centerX().toFloat()
174 maxX -= virtBounds.centerX().toFloat()
175 touchY -= virtBounds.centerY().toFloat()
176 maxY -= virtBounds.centerY().toFloat()
177 val axisX = touchX / maxX
178 val axisY = touchY / maxY
179 val oldXAxis = xAxis
180 val oldYAxis = yAxis
181
182 // Clamp the circle pad input to a circle
183 val angle = atan2(axisY.toDouble(), axisX.toDouble()).toFloat()
184 var radius = sqrt((axisX * axisX + axisY * axisY).toDouble()).toFloat()
185 if (radius > 1.0f) {
186 radius = 1.0f
187 }
188 xAxis = cos(angle.toDouble()).toFloat() * radius
189 yAxis = sin(angle.toDouble()).toFloat() * radius
190 setInnerBounds()
191 return oldXAxis != xAxis && oldYAxis != yAxis
192 }
193 return false
194 }
195
196 fun onConfigureTouch(event: MotionEvent): Boolean {
197 val pointerIndex = event.actionIndex
198 val fingerPositionX = event.getX(pointerIndex).toInt()
199 val fingerPositionY = event.getY(pointerIndex).toInt()
200
201 when (event.action) {
202 MotionEvent.ACTION_DOWN -> {
203 previousTouchX = fingerPositionX
204 previousTouchY = fingerPositionY
205 controlPositionX = fingerPositionX - (width / 2)
206 controlPositionY = fingerPositionY - (height / 2)
207 }
208
209 MotionEvent.ACTION_MOVE -> {
210 controlPositionX += fingerPositionX - previousTouchX
211 controlPositionY += fingerPositionY - previousTouchY
212 bounds = Rect(
213 controlPositionX,
214 controlPositionY,
215 outerBitmap.intrinsicWidth + controlPositionX,
216 outerBitmap.intrinsicHeight + controlPositionY
217 )
218 virtBounds = Rect(
219 controlPositionX,
220 controlPositionY,
221 outerBitmap.intrinsicWidth + controlPositionX,
222 outerBitmap.intrinsicHeight + controlPositionY
223 )
224 setInnerBounds()
225 bounds = Rect(
226 Rect(
227 controlPositionX,
228 controlPositionY,
229 outerBitmap.intrinsicWidth + controlPositionX,
230 outerBitmap.intrinsicHeight + controlPositionY
231 )
232 )
233 previousTouchX = fingerPositionX
234 previousTouchY = fingerPositionY
235 }
236 }
237 origBounds = outerBitmap.copyBounds()
238 return true
239 }
240
241 private fun setInnerBounds() {
242 var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
243 var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
244 if (x > virtBounds.centerX() + virtBounds.width() / 2) x =
245 virtBounds.centerX() + virtBounds.width() / 2
246 if (x < virtBounds.centerX() - virtBounds.width() / 2) x =
247 virtBounds.centerX() - virtBounds.width() / 2
248 if (y > virtBounds.centerY() + virtBounds.height() / 2) y =
249 virtBounds.centerY() + virtBounds.height() / 2
250 if (y < virtBounds.centerY() - virtBounds.height() / 2) y =
251 virtBounds.centerY() - virtBounds.height() / 2
252 val width = pressedStateInnerBitmap.bounds.width() / 2
253 val height = pressedStateInnerBitmap.bounds.height() / 2
254 defaultStateInnerBitmap.setBounds(
255 x - width,
256 y - height,
257 x + width,
258 y + height
259 )
260 pressedStateInnerBitmap.bounds = defaultStateInnerBitmap.bounds
261 }
262
263 fun setPosition(x: Int, y: Int) {
264 controlPositionX = x
265 controlPositionY = y
266 }
267
268 fun setOpacity(value: Int) {
269 opacity = value
270
271 defaultStateInnerBitmap.alpha = value
272 pressedStateInnerBitmap.alpha = value
273
274 if (trackId == -1) {
275 outerBitmap.alpha = value
276 boundsBoxBitmap.alpha = 0
277 } else {
278 outerBitmap.alpha = 0
279 boundsBoxBitmap.alpha = value
280 }
281 }
282}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
new file mode 100644
index 000000000..97eef40d2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
@@ -0,0 +1,165 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.ui
5
6import android.os.Bundle
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import android.view.ViewGroup.MarginLayoutParams
11import androidx.appcompat.app.AppCompatActivity
12import androidx.core.view.ViewCompat
13import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding
15import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels
17import com.google.android.material.color.MaterialColors
18import com.google.android.material.transition.MaterialFadeThrough
19import org.yuzu.yuzu_emu.R
20import org.yuzu.yuzu_emu.adapters.GameAdapter
21import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
22import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
23import org.yuzu.yuzu_emu.model.GamesViewModel
24import org.yuzu.yuzu_emu.model.HomeViewModel
25
26class GamesFragment : Fragment() {
27 private var _binding: FragmentGamesBinding? = null
28 private val binding get() = _binding!!
29
30 private val gamesViewModel: GamesViewModel by activityViewModels()
31 private val homeViewModel: HomeViewModel by activityViewModels()
32
33 override fun onCreate(savedInstanceState: Bundle?) {
34 super.onCreate(savedInstanceState)
35 enterTransition = MaterialFadeThrough()
36 }
37
38 override fun onCreateView(
39 inflater: LayoutInflater,
40 container: ViewGroup?,
41 savedInstanceState: Bundle?
42 ): View {
43 _binding = FragmentGamesBinding.inflate(inflater)
44 return binding.root
45 }
46
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 homeViewModel.setNavigationVisibility(visible = true, animated = false)
49
50 binding.gridGames.apply {
51 layoutManager = AutofitGridLayoutManager(
52 requireContext(),
53 requireContext().resources.getDimensionPixelSize(R.dimen.card_width)
54 )
55 adapter = GameAdapter(requireActivity() as AppCompatActivity)
56 }
57
58 binding.swipeRefresh.apply {
59 // Add swipe down to refresh gesture
60 setOnRefreshListener {
61 gamesViewModel.reloadGames(false)
62 }
63
64 // Set theme color to the refresh animation's background
65 setProgressBackgroundColorSchemeColor(
66 MaterialColors.getColor(
67 binding.swipeRefresh,
68 com.google.android.material.R.attr.colorPrimary
69 )
70 )
71 setColorSchemeColors(
72 MaterialColors.getColor(
73 binding.swipeRefresh,
74 com.google.android.material.R.attr.colorOnPrimary
75 )
76 )
77
78 // Make sure the loading indicator appears even if the layout is told to refresh before being fully drawn
79 post {
80 if (_binding == null) {
81 return@post
82 }
83 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!!
84 }
85 }
86
87 gamesViewModel.apply {
88 // Watch for when we get updates to any of our games lists
89 isReloading.observe(viewLifecycleOwner) { isReloading ->
90 binding.swipeRefresh.isRefreshing = isReloading
91 }
92 games.observe(viewLifecycleOwner) {
93 (binding.gridGames.adapter as GameAdapter).submitList(it)
94 if (it.isEmpty()) {
95 binding.noticeText.visibility = View.VISIBLE
96 } else {
97 binding.noticeText.visibility = View.GONE
98 }
99 }
100 shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
101 if (shouldSwapData) {
102 (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!)
103 gamesViewModel.setShouldSwapData(false)
104 }
105 }
106
107 // Check if the user reselected the games menu item and then scroll to top of the list
108 shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll ->
109 if (shouldScroll) {
110 scrollToTop()
111 gamesViewModel.setShouldScrollToTop(false)
112 }
113 }
114 }
115
116 setInsets()
117 }
118
119 override fun onDestroyView() {
120 super.onDestroyView()
121 _binding = null
122 }
123
124 private fun scrollToTop() {
125 if (_binding != null) {
126 binding.gridGames.smoothScrollToPosition(0)
127 }
128 }
129
130 private fun setInsets() =
131 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
132 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
133 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
134 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
135 val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
136 val spacingNavigationRail =
137 resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
138
139 binding.gridGames.updatePadding(
140 top = barInsets.top + extraListSpacing,
141 bottom = barInsets.bottom + spacingNavigation + extraListSpacing
142 )
143
144 binding.swipeRefresh.setProgressViewEndTarget(
145 false,
146 barInsets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_end)
147 )
148
149 val leftInsets = barInsets.left + cutoutInsets.left
150 val rightInsets = barInsets.right + cutoutInsets.right
151 val mlpSwipe = binding.swipeRefresh.layoutParams as MarginLayoutParams
152 if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
153 mlpSwipe.leftMargin = leftInsets + spacingNavigationRail
154 mlpSwipe.rightMargin = rightInsets
155 } else {
156 mlpSwipe.leftMargin = leftInsets
157 mlpSwipe.rightMargin = rightInsets + spacingNavigationRail
158 }
159 binding.swipeRefresh.layoutParams = mlpSwipe
160
161 binding.noticeText.updatePadding(bottom = spacingNavigation)
162
163 windowInsets
164 }
165}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
new file mode 100644
index 000000000..041d16f3a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -0,0 +1,528 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.ui.main
5
6import android.content.Intent
7import android.os.Bundle
8import android.view.View
9import android.view.ViewGroup.MarginLayoutParams
10import android.view.WindowManager
11import android.view.animation.PathInterpolator
12import android.widget.Toast
13import androidx.activity.result.contract.ActivityResultContracts
14import androidx.activity.viewModels
15import androidx.appcompat.app.AppCompatActivity
16import androidx.core.content.ContextCompat
17import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
18import androidx.core.view.ViewCompat
19import androidx.core.view.WindowCompat
20import androidx.core.view.WindowInsetsCompat
21import androidx.lifecycle.lifecycleScope
22import androidx.navigation.NavController
23import androidx.navigation.fragment.NavHostFragment
24import androidx.navigation.ui.setupWithNavController
25import androidx.preference.PreferenceManager
26import com.google.android.material.color.MaterialColors
27import com.google.android.material.dialog.MaterialAlertDialogBuilder
28import com.google.android.material.navigation.NavigationBarView
29import kotlinx.coroutines.Dispatchers
30import kotlinx.coroutines.launch
31import kotlinx.coroutines.withContext
32import org.yuzu.yuzu_emu.NativeLibrary
33import org.yuzu.yuzu_emu.R
34import org.yuzu.yuzu_emu.activities.EmulationActivity
35import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
36import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
37import org.yuzu.yuzu_emu.features.settings.model.Settings
38import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
39import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
40import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
41import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
42import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
43import org.yuzu.yuzu_emu.model.GamesViewModel
44import org.yuzu.yuzu_emu.model.HomeViewModel
45import org.yuzu.yuzu_emu.utils.*
46import java.io.File
47import java.io.FilenameFilter
48import java.io.IOException
49
50class MainActivity : AppCompatActivity(), ThemeProvider {
51 private lateinit var binding: ActivityMainBinding
52
53 private val homeViewModel: HomeViewModel by viewModels()
54 private val gamesViewModel: GamesViewModel by viewModels()
55 private val settingsViewModel: SettingsViewModel by viewModels()
56
57 override var themeId: Int = 0
58
59 override fun onCreate(savedInstanceState: Bundle?) {
60 val splashScreen = installSplashScreen()
61 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
62
63 settingsViewModel.settings.loadSettings()
64
65 ThemeHelper.setTheme(this)
66
67 super.onCreate(savedInstanceState)
68
69 binding = ActivityMainBinding.inflate(layoutInflater)
70 setContentView(binding.root)
71
72 WindowCompat.setDecorFitsSystemWindows(window, false)
73 window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
74
75 window.statusBarColor =
76 ContextCompat.getColor(applicationContext, android.R.color.transparent)
77 window.navigationBarColor =
78 ContextCompat.getColor(applicationContext, android.R.color.transparent)
79
80 binding.statusBarShade.setBackgroundColor(
81 ThemeHelper.getColorWithOpacity(
82 MaterialColors.getColor(
83 binding.root,
84 com.google.android.material.R.attr.colorSurface
85 ),
86 ThemeHelper.SYSTEM_BAR_ALPHA
87 )
88 )
89 if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
90 binding.navigationBarShade.setBackgroundColor(
91 ThemeHelper.getColorWithOpacity(
92 MaterialColors.getColor(
93 binding.root,
94 com.google.android.material.R.attr.colorSurface
95 ),
96 ThemeHelper.SYSTEM_BAR_ALPHA
97 )
98 )
99 }
100
101 val navHostFragment =
102 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
103 setUpNavigation(navHostFragment.navController)
104 (binding.navigationView as NavigationBarView).setOnItemReselectedListener {
105 when (it.itemId) {
106 R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
107 R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
108 R.id.homeSettingsFragment -> SettingsActivity.launch(
109 this,
110 SettingsFile.FILE_NAME_CONFIG,
111 ""
112 )
113 }
114 }
115
116 // Prevents navigation from being drawn for a short time on recreation if set to hidden
117 if (!homeViewModel.navigationVisible.value?.first!!) {
118 binding.navigationView.visibility = View.INVISIBLE
119 binding.statusBarShade.visibility = View.INVISIBLE
120 }
121
122 homeViewModel.navigationVisible.observe(this) {
123 showNavigation(it.first, it.second)
124 }
125 homeViewModel.statusBarShadeVisible.observe(this) { visible ->
126 showStatusBarShade(visible)
127 }
128
129 // Dismiss previous notifications (should not happen unless a crash occurred)
130 EmulationActivity.stopForegroundService(this)
131
132 setInsets()
133 }
134
135 fun finishSetup(navController: NavController) {
136 navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
137 (binding.navigationView as NavigationBarView).setupWithNavController(navController)
138 showNavigation(visible = true, animated = true)
139 }
140
141 private fun setUpNavigation(navController: NavController) {
142 val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
143 .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
144
145 if (firstTimeSetup && !homeViewModel.navigatedToSetup) {
146 navController.navigate(R.id.firstTimeSetupFragment)
147 homeViewModel.navigatedToSetup = true
148 } else {
149 (binding.navigationView as NavigationBarView).setupWithNavController(navController)
150 }
151 }
152
153 private fun showNavigation(visible: Boolean, animated: Boolean) {
154 if (!animated) {
155 if (visible) {
156 binding.navigationView.visibility = View.VISIBLE
157 } else {
158 binding.navigationView.visibility = View.INVISIBLE
159 }
160 return
161 }
162
163 val smallLayout = resources.getBoolean(R.bool.small_layout)
164 binding.navigationView.animate().apply {
165 if (visible) {
166 binding.navigationView.visibility = View.VISIBLE
167 duration = 300
168 interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
169
170 if (smallLayout) {
171 binding.navigationView.translationY =
172 binding.navigationView.height.toFloat() * 2
173 translationY(0f)
174 } else {
175 if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) {
176 binding.navigationView.translationX =
177 binding.navigationView.width.toFloat() * -2
178 translationX(0f)
179 } else {
180 binding.navigationView.translationX =
181 binding.navigationView.width.toFloat() * 2
182 translationX(0f)
183 }
184 }
185 } else {
186 duration = 300
187 interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f)
188
189 if (smallLayout) {
190 translationY(binding.navigationView.height.toFloat() * 2)
191 } else {
192 if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) {
193 translationX(binding.navigationView.width.toFloat() * -2)
194 } else {
195 translationX(binding.navigationView.width.toFloat() * 2)
196 }
197 }
198 }
199 }.withEndAction {
200 if (!visible) {
201 binding.navigationView.visibility = View.INVISIBLE
202 }
203 }.start()
204 }
205
206 private fun showStatusBarShade(visible: Boolean) {
207 binding.statusBarShade.animate().apply {
208 if (visible) {
209 binding.statusBarShade.visibility = View.VISIBLE
210 binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2
211 duration = 300
212 translationY(0f)
213 interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
214 } else {
215 duration = 300
216 translationY(binding.navigationView.height.toFloat() * -2)
217 interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f)
218 }
219 }.withEndAction {
220 if (!visible) {
221 binding.statusBarShade.visibility = View.INVISIBLE
222 }
223 }.start()
224 }
225
226 override fun onResume() {
227 ThemeHelper.setCorrectTheme(this)
228 super.onResume()
229 }
230
231 override fun onDestroy() {
232 EmulationActivity.stopForegroundService(this)
233 super.onDestroy()
234 }
235
236 private fun setInsets() =
237 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
238 val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
239 val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams
240 mlpStatusShade.height = insets.top
241 binding.statusBarShade.layoutParams = mlpStatusShade
242
243 // The only situation where we care to have a nav bar shade is when it's at the bottom
244 // of the screen where scrolling list elements can go behind it.
245 val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
246 mlpNavShade.height = insets.bottom
247 binding.navigationBarShade.layoutParams = mlpNavShade
248
249 windowInsets
250 }
251
252 override fun setTheme(resId: Int) {
253 super.setTheme(resId)
254 themeId = resId
255 }
256
257 val getGamesDirectory =
258 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
259 if (result == null)
260 return@registerForActivityResult
261
262 contentResolver.takePersistableUriPermission(
263 result,
264 Intent.FLAG_GRANT_READ_URI_PERMISSION
265 )
266
267 // When a new directory is picked, we currently will reset the existing games
268 // database. This effectively means that only one game directory is supported.
269 PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
270 .putString(GameHelper.KEY_GAME_PATH, result.toString())
271 .apply()
272
273 Toast.makeText(
274 applicationContext,
275 R.string.games_dir_selected,
276 Toast.LENGTH_LONG
277 ).show()
278
279 gamesViewModel.reloadGames(true)
280 }
281
282 val getProdKey =
283 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
284 if (result == null)
285 return@registerForActivityResult
286
287 if (!FileUtil.hasExtension(result, "keys")) {
288 MessageDialogFragment.newInstance(
289 R.string.reading_keys_failure,
290 R.string.install_prod_keys_failure_extension_description
291 ).show(supportFragmentManager, MessageDialogFragment.TAG)
292 return@registerForActivityResult
293 }
294
295 contentResolver.takePersistableUriPermission(
296 result,
297 Intent.FLAG_GRANT_READ_URI_PERMISSION
298 )
299
300 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
301 if (FileUtil.copyUriToInternalStorage(
302 applicationContext,
303 result,
304 dstPath,
305 "prod.keys"
306 )
307 ) {
308 if (NativeLibrary.reloadKeys()) {
309 Toast.makeText(
310 applicationContext,
311 R.string.install_keys_success,
312 Toast.LENGTH_SHORT
313 ).show()
314 gamesViewModel.reloadGames(true)
315 } else {
316 MessageDialogFragment.newInstance(
317 R.string.invalid_keys_error,
318 R.string.install_keys_failure_description,
319 R.string.dumping_keys_quickstart_link
320 ).show(supportFragmentManager, MessageDialogFragment.TAG)
321 }
322 }
323 }
324
325 val getFirmware =
326 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
327 if (result == null)
328 return@registerForActivityResult
329
330 val inputZip = contentResolver.openInputStream(result)
331 if (inputZip == null) {
332 Toast.makeText(
333 applicationContext,
334 getString(R.string.fatal_error),
335 Toast.LENGTH_LONG
336 ).show()
337 return@registerForActivityResult
338 }
339
340 val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
341
342 val firmwarePath =
343 File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
344 val cacheFirmwareDir = File("${cacheDir.path}/registered/")
345
346 val task: () -> Any = {
347 var messageToShow: Any
348 try {
349 FileUtil.unzip(inputZip, cacheFirmwareDir)
350 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
351 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
352 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
353 MessageDialogFragment.newInstance(
354 R.string.firmware_installed_failure,
355 R.string.firmware_installed_failure_description
356 )
357 } else {
358 firmwarePath.deleteRecursively()
359 cacheFirmwareDir.copyRecursively(firmwarePath, true)
360 getString(R.string.save_file_imported_success)
361 }
362 } catch (e: Exception) {
363 messageToShow = getString(R.string.fatal_error)
364 } finally {
365 cacheFirmwareDir.deleteRecursively()
366 }
367 messageToShow
368 }
369
370 IndeterminateProgressDialogFragment.newInstance(
371 this,
372 R.string.firmware_installing,
373 task
374 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
375 }
376
377 val getAmiiboKey =
378 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
379 if (result == null)
380 return@registerForActivityResult
381
382 if (!FileUtil.hasExtension(result, "bin")) {
383 MessageDialogFragment.newInstance(
384 R.string.reading_keys_failure,
385 R.string.install_amiibo_keys_failure_extension_description
386 ).show(supportFragmentManager, MessageDialogFragment.TAG)
387 return@registerForActivityResult
388 }
389
390 contentResolver.takePersistableUriPermission(
391 result,
392 Intent.FLAG_GRANT_READ_URI_PERMISSION
393 )
394
395 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
396 if (FileUtil.copyUriToInternalStorage(
397 applicationContext,
398 result,
399 dstPath,
400 "key_retail.bin"
401 )
402 ) {
403 if (NativeLibrary.reloadKeys()) {
404 Toast.makeText(
405 applicationContext,
406 R.string.install_keys_success,
407 Toast.LENGTH_SHORT
408 ).show()
409 } else {
410 MessageDialogFragment.newInstance(
411 R.string.invalid_keys_error,
412 R.string.install_keys_failure_description,
413 R.string.dumping_keys_quickstart_link
414 ).show(supportFragmentManager, MessageDialogFragment.TAG)
415 }
416 }
417 }
418
419 val getDriver =
420 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
421 if (result == null)
422 return@registerForActivityResult
423
424 val takeFlags =
425 Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
426 contentResolver.takePersistableUriPermission(
427 result,
428 takeFlags
429 )
430
431 val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
432 progressBinding.progressBar.isIndeterminate = true
433 val installationDialog = MaterialAlertDialogBuilder(this)
434 .setTitle(R.string.installing_driver)
435 .setView(progressBinding.root)
436 .show()
437
438 lifecycleScope.launch {
439 withContext(Dispatchers.IO) {
440 // Ignore file exceptions when a user selects an invalid zip
441 try {
442 GpuDriverHelper.installCustomDriver(applicationContext, result)
443 } catch (_: IOException) {
444 }
445
446 withContext(Dispatchers.Main) {
447 installationDialog.dismiss()
448
449 val driverName = GpuDriverHelper.customDriverName
450 if (driverName != null) {
451 Toast.makeText(
452 applicationContext,
453 getString(
454 R.string.select_gpu_driver_install_success,
455 driverName
456 ),
457 Toast.LENGTH_SHORT
458 ).show()
459 } else {
460 Toast.makeText(
461 applicationContext,
462 R.string.select_gpu_driver_error,
463 Toast.LENGTH_LONG
464 ).show()
465 }
466 }
467 }
468 }
469 }
470
471 val installGameUpdate =
472 registerForActivityResult(ActivityResultContracts.OpenDocument()) {
473 if (it == null)
474 return@registerForActivityResult
475
476 IndeterminateProgressDialogFragment.newInstance(
477 this@MainActivity,
478 R.string.install_game_content
479 ) {
480 val result = NativeLibrary.installFileToNand(it.toString())
481 lifecycleScope.launch {
482 withContext(Dispatchers.Main) {
483 when (result) {
484 NativeLibrary.InstallFileToNandResult.Success -> {
485 Toast.makeText(
486 applicationContext,
487 R.string.install_game_content_success,
488 Toast.LENGTH_SHORT
489 ).show()
490 }
491
492 NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
493 Toast.makeText(
494 applicationContext,
495 R.string.install_game_content_success_overwrite,
496 Toast.LENGTH_SHORT
497 ).show()
498 }
499
500 NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
501 MessageDialogFragment.newInstance(
502 R.string.install_game_content_failure,
503 R.string.install_game_content_failure_base
504 ).show(supportFragmentManager, MessageDialogFragment.TAG)
505 }
506
507 NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
508 MessageDialogFragment.newInstance(
509 R.string.install_game_content_failure,
510 R.string.install_game_content_failure_file_extension,
511 R.string.install_game_content_help_link
512 ).show(supportFragmentManager, MessageDialogFragment.TAG)
513 }
514
515 else -> {
516 MessageDialogFragment.newInstance(
517 R.string.install_game_content_failure,
518 R.string.install_game_content_failure_description,
519 R.string.install_game_content_help_link
520 ).show(supportFragmentManager, MessageDialogFragment.TAG)
521 }
522 }
523 }
524 }
525 return@newInstance result
526 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
527 }
528}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/ThemeProvider.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/ThemeProvider.kt
new file mode 100644
index 000000000..511a6e4fa
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/ThemeProvider.kt
@@ -0,0 +1,11 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.ui.main
5
6interface ThemeProvider {
7 /**
8 * Provides theme ID by overriding an activity's 'setTheme' method and returning that result
9 */
10 var themeId: Int
11}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
new file mode 100644
index 000000000..9cfda74ee
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6class BiMap<K, V> {
7 private val forward: MutableMap<K, V> = HashMap()
8 private val backward: MutableMap<V, K> = HashMap()
9
10 @Synchronized
11 fun add(key: K, value: V) {
12 forward[key] = value
13 backward[value] = key
14 }
15
16 @Synchronized
17 fun getForward(key: K): V? {
18 return forward[key]
19 }
20
21 @Synchronized
22 fun getBackward(key: V): K? {
23 return backward[key]
24 }
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt
new file mode 100644
index 000000000..791cea904
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt
@@ -0,0 +1,68 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.view.InputDevice
7import android.view.KeyEvent
8import android.view.MotionEvent
9
10/**
11 * Some controllers have incorrect mappings. This class has special-case fixes for them.
12 */
13class ControllerMappingHelper {
14 /**
15 * Some controllers report extra button presses that can be ignored.
16 */
17 fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
18 return if (isDualShock4(inputDevice)) {
19 // The two analog triggers generate analog motion events as well as a keycode.
20 // We always prefer to use the analog values, so throw away the button press
21 keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
22 } else false
23 }
24
25 /**
26 * Scale an axis to be zero-centered with a proper range.
27 */
28 fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float {
29 if (isDualShock4(inputDevice)) {
30 // Android doesn't have correct mappings for this controller's triggers. It reports them
31 // as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
32 // Scale them to properly zero-centered with a range of [0.0, 1.0].
33 if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
34 return (value + 1) / 2.0f
35 }
36 } else if (isXboxOneWireless(inputDevice)) {
37 // Same as the DualShock 4, the mappings are missing.
38 if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
39 return (value + 1) / 2.0f
40 }
41 if (axis == MotionEvent.AXIS_GENERIC_1) {
42 // This axis is stuck at ~.5. Ignore it.
43 return 0.0f
44 }
45 } else if (isMogaPro2Hid(inputDevice)) {
46 // This controller has a broken axis that reports a constant value. Ignore it.
47 if (axis == MotionEvent.AXIS_GENERIC_1) {
48 return 0.0f
49 }
50 }
51 return value
52 }
53
54 // Sony DualShock 4 controller
55 private fun isDualShock4(inputDevice: InputDevice): Boolean {
56 return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc
57 }
58
59 // Microsoft Xbox One controller
60 private fun isXboxOneWireless(inputDevice: InputDevice): Boolean {
61 return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0
62 }
63
64 // Moga Pro 2 HID
65 private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean {
66 return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271
67 }
68}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
new file mode 100644
index 000000000..36c479e6c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -0,0 +1,37 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.content.Context
7import org.yuzu.yuzu_emu.NativeLibrary
8import java.io.IOException
9
10object DirectoryInitialization {
11 private var userPath: String? = null
12
13 var areDirectoriesReady: Boolean = false
14
15 fun start(context: Context) {
16 if (!areDirectoriesReady) {
17 initializeInternalStorage(context)
18 NativeLibrary.initializeEmulation()
19 areDirectoriesReady = true
20 }
21 }
22
23 val userDirectory: String?
24 get() {
25 check(areDirectoriesReady) { "Directory initialization is not ready!" }
26 return userPath
27 }
28
29 private fun initializeInternalStorage(context: Context) {
30 try {
31 userPath = context.getExternalFilesDir(null)!!.canonicalPath
32 NativeLibrary.setAppDirectory(userPath!!)
33 } catch (e: IOException) {
34 e.printStackTrace()
35 }
36 }
37}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
new file mode 100644
index 000000000..cc8ea6b9d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
@@ -0,0 +1,112 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.net.Uri
7import androidx.documentfile.provider.DocumentFile
8import org.yuzu.yuzu_emu.YuzuApplication
9import org.yuzu.yuzu_emu.model.MinimalDocumentFile
10import java.io.File
11import java.util.*
12
13class DocumentsTree {
14 private var root: DocumentsNode? = null
15
16 fun setRoot(rootUri: Uri?) {
17 root = null
18 root = DocumentsNode()
19 root!!.uri = rootUri
20 root!!.isDirectory = true
21 }
22
23 fun openContentUri(filepath: String, openMode: String?): Int {
24 val node = resolvePath(filepath) ?: return -1
25 return FileUtil.openContentUri(YuzuApplication.appContext, node.uri.toString(), openMode)
26 }
27
28 fun getFileSize(filepath: String): Long {
29 val node = resolvePath(filepath)
30 return if (node == null || node.isDirectory) {
31 0
32 } else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
33 }
34
35 fun exists(filepath: String): Boolean {
36 return resolvePath(filepath) != null
37 }
38
39 private fun resolvePath(filepath: String): DocumentsNode? {
40 val tokens = StringTokenizer(filepath, File.separator, false)
41 var iterator = root
42 while (tokens.hasMoreTokens()) {
43 val token = tokens.nextToken()
44 if (token.isEmpty()) continue
45 iterator = find(iterator, token)
46 if (iterator == null) return null
47 }
48 return iterator
49 }
50
51 private fun find(parent: DocumentsNode?, filename: String): DocumentsNode? {
52 if (parent!!.isDirectory && !parent.loaded) {
53 structTree(parent)
54 }
55 return parent.children[filename]
56 }
57
58 /**
59 * Construct current level directory tree
60 * @param parent parent node of this level
61 */
62 private fun structTree(parent: DocumentsNode) {
63 val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!)
64 for (document in documents) {
65 val node = DocumentsNode(document)
66 node.parent = parent
67 parent.children[node.name] = node
68 }
69 parent.loaded = true
70 }
71
72 private class DocumentsNode {
73 var parent: DocumentsNode? = null
74 val children: MutableMap<String?, DocumentsNode> = HashMap()
75 var name: String? = null
76 var uri: Uri? = null
77 var loaded = false
78 var isDirectory = false
79
80 constructor()
81 constructor(document: MinimalDocumentFile) {
82 name = document.filename
83 uri = document.uri
84 isDirectory = document.isDirectory
85 loaded = !isDirectory
86 }
87
88 private constructor(document: DocumentFile, isCreateDir: Boolean) {
89 name = document.name
90 uri = document.uri
91 isDirectory = isCreateDir
92 loaded = true
93 }
94
95 private fun rename(name: String) {
96 if (parent == null) {
97 return
98 }
99 parent!!.children.remove(this.name)
100 this.name = name
101 parent!!.children[name] = this
102 }
103 }
104
105 companion object {
106 fun isNativePath(path: String): Boolean {
107 return if (path.isNotEmpty()) {
108 path[0] == '/'
109 } else false
110 }
111 }
112}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt
new file mode 100644
index 000000000..e1e7a59d7
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt
@@ -0,0 +1,68 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import androidx.preference.PreferenceManager
7import org.yuzu.yuzu_emu.YuzuApplication
8import org.yuzu.yuzu_emu.features.settings.model.Settings
9
10object EmulationMenuSettings {
11 private val preferences =
12 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
13
14 // These must match what is defined in src/core/settings.h
15 const val LayoutOption_Default = 0
16 const val LayoutOption_SingleScreen = 1
17 const val LayoutOption_LargeScreen = 2
18 const val LayoutOption_SideScreen = 3
19 const val LayoutOption_MobilePortrait = 4
20 const val LayoutOption_MobileLandscape = 5
21
22 var joystickRelCenter: Boolean
23 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
24 set(value) {
25 preferences.edit()
26 .putBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, value)
27 .apply()
28 }
29 var dpadSlide: Boolean
30 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, true)
31 set(value) {
32 preferences.edit()
33 .putBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, value)
34 .apply()
35 }
36 var hapticFeedback: Boolean
37 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, false)
38 set(value) {
39 preferences.edit()
40 .putBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, value)
41 .apply()
42 }
43
44 var landscapeScreenLayout: Int
45 get() = preferences.getInt(
46 Settings.PREF_MENU_SETTINGS_LANDSCAPE,
47 LayoutOption_MobileLandscape
48 )
49 set(value) {
50 preferences.edit()
51 .putInt(Settings.PREF_MENU_SETTINGS_LANDSCAPE, value)
52 .apply()
53 }
54 var showFps: Boolean
55 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
56 set(value) {
57 preferences.edit()
58 .putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, value)
59 .apply()
60 }
61 var showOverlay: Boolean
62 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, true)
63 set(value) {
64 preferences.edit()
65 .putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, value)
66 .apply()
67 }
68}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
new file mode 100644
index 000000000..492b1ad91
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -0,0 +1,350 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.content.Context
7import android.database.Cursor
8import android.net.Uri
9import android.provider.DocumentsContract
10import android.provider.OpenableColumns
11import androidx.documentfile.provider.DocumentFile
12import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.model.MinimalDocumentFile
14import java.io.BufferedInputStream
15import java.io.File
16import java.io.FileOutputStream
17import java.io.IOException
18import java.io.InputStream
19import java.net.URLDecoder
20import java.util.zip.ZipEntry
21import java.util.zip.ZipInputStream
22
23object FileUtil {
24 const val PATH_TREE = "tree"
25 const val DECODE_METHOD = "UTF-8"
26 const val APPLICATION_OCTET_STREAM = "application/octet-stream"
27 const val TEXT_PLAIN = "text/plain"
28
29 /**
30 * Create a file from directory with filename.
31 * @param context Application context
32 * @param directory parent path for file.
33 * @param filename file display name.
34 * @return boolean
35 */
36 fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? {
37 var decodedFilename = filename
38 try {
39 val directoryUri = Uri.parse(directory)
40 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
41 decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
42 var mimeType = APPLICATION_OCTET_STREAM
43 if (decodedFilename.endsWith(".txt")) {
44 mimeType = TEXT_PLAIN
45 }
46 val exists = parent.findFile(decodedFilename)
47 return exists ?: parent.createFile(mimeType, decodedFilename)
48 } catch (e: Exception) {
49 Log.error("[FileUtil]: Cannot create file, error: " + e.message)
50 }
51 return null
52 }
53
54 /**
55 * Create a directory from directory with filename.
56 * @param context Application context
57 * @param directory parent path for directory.
58 * @param directoryName directory display name.
59 * @return boolean
60 */
61 fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? {
62 var decodedDirectoryName = directoryName
63 try {
64 val directoryUri = Uri.parse(directory)
65 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
66 decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
67 val isExist = parent.findFile(decodedDirectoryName)
68 return isExist ?: parent.createDirectory(decodedDirectoryName)
69 } catch (e: Exception) {
70 Log.error("[FileUtil]: Cannot create file, error: " + e.message)
71 }
72 return null
73 }
74
75 /**
76 * Open content uri and return file descriptor to JNI.
77 * @param context Application context
78 * @param path Native content uri path
79 * @param openMode will be one of "r", "r", "rw", "wa", "rwa"
80 * @return file descriptor
81 */
82 @JvmStatic
83 fun openContentUri(context: Context, path: String, openMode: String?): Int {
84 try {
85 val uri = Uri.parse(path)
86 val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
87 if (parcelFileDescriptor == null) {
88 Log.error("[FileUtil]: Cannot get the file descriptor from uri: $path")
89 return -1
90 }
91 val fileDescriptor = parcelFileDescriptor.detachFd()
92 parcelFileDescriptor.close()
93 return fileDescriptor
94 } catch (e: Exception) {
95 Log.error("[FileUtil]: Cannot open content uri, error: " + e.message)
96 }
97 return -1
98 }
99
100 /**
101 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
102 * This function will be faster than DoucmentFile.listFiles
103 * @param context Application context
104 * @param uri Directory uri.
105 * @return CheapDocument lists.
106 */
107 fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> {
108 val resolver = context.contentResolver
109 val columns = arrayOf(
110 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
111 DocumentsContract.Document.COLUMN_DISPLAY_NAME,
112 DocumentsContract.Document.COLUMN_MIME_TYPE
113 )
114 var c: Cursor? = null
115 val results: MutableList<MinimalDocumentFile> = ArrayList()
116 try {
117 val docId: String = if (isRootTreeUri(uri)) {
118 DocumentsContract.getTreeDocumentId(uri)
119 } else {
120 DocumentsContract.getDocumentId(uri)
121 }
122 val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId)
123 c = resolver.query(childrenUri, columns, null, null, null)
124 while (c!!.moveToNext()) {
125 val documentId = c.getString(0)
126 val documentName = c.getString(1)
127 val documentMimeType = c.getString(2)
128 val documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId)
129 val document = MinimalDocumentFile(documentName, documentMimeType, documentUri)
130 results.add(document)
131 }
132 } catch (e: Exception) {
133 Log.error("[FileUtil]: Cannot list file error: " + e.message)
134 } finally {
135 closeQuietly(c)
136 }
137 return results.toTypedArray()
138 }
139
140 /**
141 * Check whether given path exists.
142 * @param path Native content uri path
143 * @return bool
144 */
145 fun exists(context: Context, path: String?): Boolean {
146 var c: Cursor? = null
147 try {
148 val mUri = Uri.parse(path)
149 val columns = arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
150 c = context.contentResolver.query(mUri, columns, null, null, null)
151 return c!!.count > 0
152 } catch (e: Exception) {
153 Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
154 } finally {
155 closeQuietly(c)
156 }
157 return false
158 }
159
160 /**
161 * Check whether given path is a directory
162 * @param path content uri path
163 * @return bool
164 */
165 fun isDirectory(context: Context, path: String): Boolean {
166 val resolver = context.contentResolver
167 val columns = arrayOf(
168 DocumentsContract.Document.COLUMN_MIME_TYPE
169 )
170 var isDirectory = false
171 var c: Cursor? = null
172 try {
173 val mUri = Uri.parse(path)
174 c = resolver.query(mUri, columns, null, null, null)
175 c!!.moveToNext()
176 val mimeType = c.getString(0)
177 isDirectory = mimeType == DocumentsContract.Document.MIME_TYPE_DIR
178 } catch (e: Exception) {
179 Log.error("[FileUtil]: Cannot list files, error: " + e.message)
180 } finally {
181 closeQuietly(c)
182 }
183 return isDirectory
184 }
185
186 /**
187 * Get file display name from given path
188 * @param path content uri path
189 * @return String display name
190 */
191 fun getFilename(context: Context, path: String): String {
192 val resolver = context.contentResolver
193 val columns = arrayOf(
194 DocumentsContract.Document.COLUMN_DISPLAY_NAME
195 )
196 var filename = ""
197 var c: Cursor? = null
198 try {
199 val mUri = Uri.parse(path)
200 c = resolver.query(mUri, columns, null, null, null)
201 c!!.moveToNext()
202 filename = c.getString(0)
203 } catch (e: Exception) {
204 Log.error("[FileUtil]: Cannot get file size, error: " + e.message)
205 } finally {
206 closeQuietly(c)
207 }
208 return filename
209 }
210
211 fun getFilesName(context: Context, path: String): Array<String> {
212 val uri = Uri.parse(path)
213 val files: MutableList<String> = ArrayList()
214 for (file in listFiles(context, uri)) {
215 files.add(file.filename)
216 }
217 return files.toTypedArray()
218 }
219
220 /**
221 * Get file size from given path.
222 * @param path content uri path
223 * @return long file size
224 */
225 @JvmStatic
226 fun getFileSize(context: Context, path: String): Long {
227 val resolver = context.contentResolver
228 val columns = arrayOf(
229 DocumentsContract.Document.COLUMN_SIZE
230 )
231 var size: Long = 0
232 var c: Cursor? = null
233 try {
234 val mUri = Uri.parse(path)
235 c = resolver.query(mUri, columns, null, null, null)
236 c!!.moveToNext()
237 size = c.getLong(0)
238 } catch (e: Exception) {
239 Log.error("[FileUtil]: Cannot get file size, error: " + e.message)
240 } finally {
241 closeQuietly(c)
242 }
243 return size
244 }
245
246 fun copyUriToInternalStorage(
247 context: Context,
248 sourceUri: Uri?,
249 destinationParentPath: String,
250 destinationFilename: String
251 ): Boolean {
252 var input: InputStream? = null
253 var output: FileOutputStream? = null
254 try {
255 input = context.contentResolver.openInputStream(sourceUri!!)
256 output = FileOutputStream("$destinationParentPath/$destinationFilename")
257 val buffer = ByteArray(1024)
258 var len: Int
259 while (input!!.read(buffer).also { len = it } != -1) {
260 output.write(buffer, 0, len)
261 }
262 output.flush()
263 return true
264 } catch (e: Exception) {
265 Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
266 } finally {
267 if (input != null) {
268 try {
269 input.close()
270 } catch (e: IOException) {
271 Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
272 }
273 }
274 if (output != null) {
275 try {
276 output.close()
277 } catch (e: IOException) {
278 Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
279 }
280 }
281 }
282 return false
283 }
284
285 /**
286 * Extracts the given zip file into the given directory.
287 * @exception IOException if the file was being created outside of the target directory
288 */
289 @Throws(SecurityException::class)
290 fun unzip(zipStream: InputStream, destDir: File): Boolean {
291 ZipInputStream(BufferedInputStream(zipStream)).use { zis ->
292 var entry: ZipEntry? = zis.nextEntry
293 while (entry != null) {
294 val entryName = entry.name
295 val entryFile = File(destDir, entryName)
296 if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
297 throw SecurityException("Entry is outside of the target dir: " + entryFile.name)
298 }
299 if (entry.isDirectory) {
300 entryFile.mkdirs()
301 } else {
302 entryFile.parentFile?.mkdirs()
303 entryFile.createNewFile()
304 entryFile.outputStream().use { fos -> zis.copyTo(fos) }
305 }
306 entry = zis.nextEntry
307 }
308 }
309
310 return true
311 }
312
313 fun isRootTreeUri(uri: Uri): Boolean {
314 val paths = uri.pathSegments
315 return paths.size == 2 && PATH_TREE == paths[0]
316 }
317
318 fun closeQuietly(closeable: AutoCloseable?) {
319 if (closeable != null) {
320 try {
321 closeable.close()
322 } catch (rethrown: RuntimeException) {
323 throw rethrown
324 } catch (ignored: Exception) {
325 }
326 }
327 }
328
329 fun hasExtension(path: String, extension: String): Boolean =
330 path.substring(path.lastIndexOf(".") + 1).contains(extension)
331
332 fun hasExtension(uri: Uri, extension: String): Boolean {
333 val fileName: String?
334 val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
335 val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
336 cursor?.moveToFirst()
337
338 if (nameIndex == null) {
339 return false
340 }
341
342 fileName = cursor.getString(nameIndex)
343 cursor.close()
344
345 if (fileName == null) {
346 return false
347 }
348 return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
349 }
350}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt
new file mode 100644
index 000000000..dc9b7c744
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.app.PendingIntent
7import android.app.Service
8import android.content.Intent
9import android.os.IBinder
10import androidx.core.app.NotificationCompat
11import androidx.core.app.NotificationManagerCompat
12import org.yuzu.yuzu_emu.R
13import org.yuzu.yuzu_emu.activities.EmulationActivity
14
15/**
16 * A service that shows a permanent notification in the background to avoid the app getting
17 * cleared from memory by the system.
18 */
19class ForegroundService : Service() {
20 companion object {
21 const val EMULATION_RUNNING_NOTIFICATION = 0x1000
22
23 const val ACTION_STOP = "stop"
24 }
25
26 private fun showRunningNotification() {
27 // Intent is used to resume emulation if the notification is clicked
28 val contentIntent = PendingIntent.getActivity(
29 this,
30 0,
31 Intent(this, EmulationActivity::class.java),
32 PendingIntent.FLAG_IMMUTABLE
33 )
34 val builder =
35 NotificationCompat.Builder(this, getString(R.string.emulation_notification_channel_id))
36 .setSmallIcon(R.drawable.ic_stat_notification_logo)
37 .setContentTitle(getString(R.string.app_name))
38 .setContentText(getString(R.string.emulation_notification_running))
39 .setPriority(NotificationCompat.PRIORITY_LOW)
40 .setOngoing(true)
41 .setVibrate(null)
42 .setSound(null)
43 .setContentIntent(contentIntent)
44 startForeground(EMULATION_RUNNING_NOTIFICATION, builder.build())
45 }
46
47 override fun onBind(intent: Intent): IBinder? {
48 return null
49 }
50
51 override fun onCreate() {
52 showRunningNotification()
53 }
54
55 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
56 if (intent == null) {
57 return START_NOT_STICKY;
58 }
59 if (intent.action == ACTION_STOP) {
60 NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
61 stopForeground(STOP_FOREGROUND_REMOVE)
62 stopSelfResult(startId)
63 }
64 return START_STICKY
65 }
66
67 override fun onDestroy() {
68 NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
69 }
70}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
new file mode 100644
index 000000000..42b207618
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -0,0 +1,98 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.content.SharedPreferences
7import android.net.Uri
8import androidx.preference.PreferenceManager
9import kotlinx.serialization.encodeToString
10import kotlinx.serialization.json.Json
11import org.yuzu.yuzu_emu.NativeLibrary
12import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.model.Game
14import java.util.*
15
16object GameHelper {
17 const val KEY_GAME_PATH = "game_path"
18 const val KEY_GAMES = "Games"
19
20 private lateinit var preferences: SharedPreferences
21
22 fun getGames(): List<Game> {
23 val games = mutableListOf<Game>()
24 val context = YuzuApplication.appContext
25 val gamesDir =
26 PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_GAME_PATH, "")
27 val gamesUri = Uri.parse(gamesDir)
28 preferences = PreferenceManager.getDefaultSharedPreferences(context)
29
30 // Ensure keys are loaded so that ROM metadata can be decrypted.
31 NativeLibrary.reloadKeys()
32
33 val children = FileUtil.listFiles(context, gamesUri)
34 for (file in children) {
35 if (!file.isDirectory) {
36 val filename = file.uri.toString()
37 val extensionStart = filename.lastIndexOf('.')
38 if (extensionStart > 0) {
39 val fileExtension = filename.substring(extensionStart)
40
41 // Check that the file has an extension we care about before trying to read out of it.
42 if (Game.extensions.contains(fileExtension.lowercase(Locale.getDefault()))) {
43 games.add(getGame(filename))
44 }
45 }
46 }
47 }
48
49 // Cache list of games found on disk
50 val serializedGames = mutableSetOf<String>()
51 games.forEach {
52 serializedGames.add(Json.encodeToString(it))
53 }
54 preferences.edit()
55 .remove(KEY_GAMES)
56 .putStringSet(KEY_GAMES, serializedGames)
57 .apply()
58
59 return games.toList()
60 }
61
62 private fun getGame(filePath: String): Game {
63 var name = NativeLibrary.getTitle(filePath)
64
65 // If the game's title field is empty, use the filename.
66 if (name.isEmpty()) {
67 name = filePath.substring(filePath.lastIndexOf("/") + 1)
68 }
69 var gameId = NativeLibrary.getGameId(filePath)
70
71 // If the game's ID field is empty, use the filename without extension.
72 if (gameId.isEmpty()) {
73 gameId = filePath.substring(
74 filePath.lastIndexOf("/") + 1,
75 filePath.lastIndexOf(".")
76 )
77 }
78
79 val newGame = Game(
80 name,
81 NativeLibrary.getDescription(filePath).replace("\n", " "),
82 NativeLibrary.getRegions(filePath),
83 filePath,
84 gameId,
85 NativeLibrary.getCompany(filePath),
86 NativeLibrary.isHomebrew(filePath)
87 )
88
89 val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
90 if (addedTime == 0L) {
91 preferences.edit()
92 .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis())
93 .apply()
94 }
95
96 return newGame
97 }
98}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
new file mode 100644
index 000000000..528011d7f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -0,0 +1,152 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.content.Context
7import android.net.Uri
8import org.yuzu.yuzu_emu.NativeLibrary
9import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
10import java.io.BufferedInputStream
11import java.io.File
12import java.io.FileInputStream
13import java.io.FileOutputStream
14import java.io.IOException
15import java.util.zip.ZipInputStream
16
17object GpuDriverHelper {
18 private const val META_JSON_FILENAME = "meta.json"
19 private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
20 private var fileRedirectionPath: String? = null
21 private var driverInstallationPath: String? = null
22 private var hookLibPath: String? = null
23
24 @Throws(IOException::class)
25 private fun unzip(zipFilePath: String, destDir: String) {
26 val dir = File(destDir)
27
28 // Create output directory if it doesn't exist
29 if (!dir.exists()) dir.mkdirs()
30
31 // Unpack the files.
32 val inputStream = FileInputStream(zipFilePath)
33 val zis = ZipInputStream(BufferedInputStream(inputStream))
34 val buffer = ByteArray(1024)
35 var ze = zis.nextEntry
36 while (ze != null) {
37 val newFile = File(destDir, ze.name)
38 val canonicalPath = newFile.canonicalPath
39 if (!canonicalPath.startsWith(destDir + ze.name)) {
40 throw SecurityException("Zip file attempted path traversal! " + ze.name)
41 }
42
43 newFile.parentFile!!.mkdirs()
44 val fos = FileOutputStream(newFile)
45 var len: Int
46 while (zis.read(buffer).also { len = it } > 0) {
47 fos.write(buffer, 0, len)
48 }
49 fos.close()
50 zis.closeEntry()
51 ze = zis.nextEntry
52 }
53 zis.closeEntry()
54 }
55
56 fun initializeDriverParameters(context: Context) {
57 try {
58 // Initialize the file redirection directory.
59 fileRedirectionPath =
60 context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
61
62 // Initialize the driver installation directory.
63 driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/"
64 } catch (e: IOException) {
65 throw RuntimeException(e)
66 }
67
68 // Initialize directories.
69 initializeDirectories()
70
71 // Initialize hook libraries directory.
72 hookLibPath = context.applicationInfo.nativeLibraryDir + "/"
73
74 // Initialize GPU driver.
75 NativeLibrary.initializeGpuDriver(
76 hookLibPath,
77 driverInstallationPath,
78 customDriverLibraryName,
79 fileRedirectionPath
80 )
81 }
82
83 fun installDefaultDriver(context: Context) {
84 // Removing the installed driver will result in the backend using the default system driver.
85 val driverInstallationDir = File(driverInstallationPath!!)
86 deleteRecursive(driverInstallationDir)
87 initializeDriverParameters(context)
88 }
89
90 fun installCustomDriver(context: Context, driverPathUri: Uri?) {
91 // Revert to system default in the event the specified driver is bad.
92 installDefaultDriver(context)
93
94 // Ensure we have directories.
95 initializeDirectories()
96
97 // Copy the zip file URI into our private storage.
98 copyUriToInternalStorage(
99 context,
100 driverPathUri,
101 driverInstallationPath!!,
102 DRIVER_INTERNAL_FILENAME
103 )
104
105 // Unzip the driver.
106 try {
107 unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
108 } catch (e: SecurityException) {
109 return
110 }
111
112 // Initialize the driver parameters.
113 initializeDriverParameters(context)
114 }
115
116 // Parse the custom driver metadata to retrieve the name.
117 val customDriverName: String?
118 get() {
119 val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
120 return metadata.name
121 }
122
123 // Parse the custom driver metadata to retrieve the library name.
124 private val customDriverLibraryName: String?
125 get() {
126 // Parse the custom driver metadata to retrieve the library name.
127 val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
128 return metadata.libraryName
129 }
130
131 private fun initializeDirectories() {
132 // Ensure the file redirection directory exists.
133 val fileRedirectionDir = File(fileRedirectionPath!!)
134 if (!fileRedirectionDir.exists()) {
135 fileRedirectionDir.mkdirs()
136 }
137 // Ensure the driver installation directory exists.
138 val driverInstallationDir = File(driverInstallationPath!!)
139 if (!driverInstallationDir.exists()) {
140 driverInstallationDir.mkdirs()
141 }
142 }
143
144 private fun deleteRecursive(fileOrDirectory: File) {
145 if (fileOrDirectory.isDirectory) {
146 for (child in fileOrDirectory.listFiles()!!) {
147 deleteRecursive(child)
148 }
149 }
150 fileOrDirectory.delete()
151 }
152}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
new file mode 100644
index 000000000..70bdb4097
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import org.json.JSONException
7import org.json.JSONObject
8import java.io.IOException
9import java.nio.charset.StandardCharsets
10import java.nio.file.Files
11import java.nio.file.Paths
12
13class GpuDriverMetadata(metadataFilePath: String) {
14 var name: String? = null
15 var description: String? = null
16 var author: String? = null
17 var vendor: String? = null
18 var driverVersion: String? = null
19 var minApi = 0
20 var libraryName: String? = null
21
22 init {
23 try {
24 val json = JSONObject(getStringFromFile(metadataFilePath))
25 name = json.getString("name")
26 description = json.getString("description")
27 author = json.getString("author")
28 vendor = json.getString("vendor")
29 driverVersion = json.getString("driverVersion")
30 minApi = json.getInt("minApi")
31 libraryName = json.getString("libraryName")
32 } catch (e: JSONException) {
33 // JSON is malformed, ignore and treat as unsupported metadata.
34 } catch (e: IOException) {
35 // File is inaccessible, ignore and treat as unsupported metadata.
36 }
37 }
38
39 companion object {
40 @Throws(IOException::class)
41 private fun getStringFromFile(filePath: String): String {
42 val path = Paths.get(filePath)
43 val bytes = Files.readAllBytes(path)
44 return String(bytes, StandardCharsets.UTF_8)
45 }
46 }
47}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
new file mode 100644
index 000000000..24e999b29
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
@@ -0,0 +1,360 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.view.KeyEvent
7import android.view.MotionEvent
8import org.yuzu.yuzu_emu.NativeLibrary
9import kotlin.math.sqrt
10
11class InputHandler {
12 fun initialize() {
13 // Connect first controller
14 NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
15 }
16
17 fun dispatchKeyEvent(event: KeyEvent): Boolean {
18 val button: Int = when (event.device.vendorId) {
19 0x045E -> getInputXboxButtonKey(event.keyCode)
20 0x054C -> getInputDS5ButtonKey(event.keyCode)
21 0x057E -> getInputJoyconButtonKey(event.keyCode)
22 0x1532 -> getInputRazerButtonKey(event.keyCode)
23 else -> getInputGenericButtonKey(event.keyCode)
24 }
25
26 val action = when (event.action) {
27 KeyEvent.ACTION_DOWN -> NativeLibrary.ButtonState.PRESSED
28 KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
29 else -> return false
30 }
31
32 // Ignore invalid buttons
33 if (button < 0) {
34 return false
35 }
36
37 return NativeLibrary.onGamePadButtonEvent(
38 getPlayerNumber(event.device.controllerNumber),
39 button,
40 action
41 )
42 }
43
44 fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
45 val device = event.device
46 // Check every axis input available on the controller
47 for (range in device.motionRanges) {
48 val axis = range.axis
49 when (device.vendorId) {
50 0x045E -> setGenericAxisInput(event, axis)
51 0x054C -> setGenericAxisInput(event, axis)
52 0x057E -> setJoyconAxisInput(event, axis)
53 0x1532 -> setRazerAxisInput(event, axis)
54 else -> setGenericAxisInput(event, axis)
55 }
56 }
57
58 return true
59 }
60
61 private fun getPlayerNumber(index: Int): Int {
62 // TODO: Joycons are handled as different controllers. Find a way to merge them.
63 return when (index) {
64 2 -> NativeLibrary.Player2Device
65 3 -> NativeLibrary.Player3Device
66 4 -> NativeLibrary.Player4Device
67 5 -> NativeLibrary.Player5Device
68 6 -> NativeLibrary.Player6Device
69 7 -> NativeLibrary.Player7Device
70 8 -> NativeLibrary.Player8Device
71 else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
72 }
73 }
74
75 private fun setStickState(playerNumber: Int, index: Int, xAxis: Float, yAxis: Float) {
76 // Calculate vector size
77 val r2 = xAxis * xAxis + yAxis * yAxis
78 var r = sqrt(r2.toDouble()).toFloat()
79
80 // Adjust range of joystick
81 val deadzone = 0.15f
82 var x = xAxis
83 var y = yAxis
84
85 if (r > deadzone) {
86 val deadzoneFactor = 1.0f / r * (r - deadzone) / (1.0f - deadzone)
87 x *= deadzoneFactor
88 y *= deadzoneFactor
89 r *= deadzoneFactor
90 } else {
91 x = 0.0f
92 y = 0.0f
93 }
94
95 // Normalize joystick
96 if (r > 1.0f) {
97 x /= r
98 y /= r
99 }
100
101 NativeLibrary.onGamePadJoystickEvent(
102 playerNumber,
103 index,
104 x,
105 -y
106 )
107 }
108
109 private fun getAxisToButton(axis: Float): Int {
110 return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED
111 }
112
113 private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
114 NativeLibrary.onGamePadButtonEvent(
115 playerNumber,
116 NativeLibrary.ButtonType.DPAD_UP,
117 getAxisToButton(-yAxis)
118 )
119 NativeLibrary.onGamePadButtonEvent(
120 playerNumber,
121 NativeLibrary.ButtonType.DPAD_DOWN,
122 getAxisToButton(yAxis)
123 )
124 NativeLibrary.onGamePadButtonEvent(
125 playerNumber,
126 NativeLibrary.ButtonType.DPAD_LEFT,
127 getAxisToButton(-xAxis)
128 )
129 NativeLibrary.onGamePadButtonEvent(
130 playerNumber,
131 NativeLibrary.ButtonType.DPAD_RIGHT,
132 getAxisToButton(xAxis)
133 )
134 }
135
136 private fun getInputDS5ButtonKey(key: Int): Int {
137 // The missing ds5 buttons are axis
138 return when (key) {
139 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
140 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
141 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
142 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
143 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
144 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
145 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
146 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
147 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
148 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
149 else -> -1
150 }
151 }
152
153 private fun getInputJoyconButtonKey(key: Int): Int {
154 // Joycon support is half dead. A lot of buttons can't be mapped
155 return when (key) {
156 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
157 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
158 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
159 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
160 KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
161 KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
162 KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
163 KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
164 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
165 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
166 KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
167 KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
168 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
169 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
170 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
171 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
172 else -> -1
173 }
174 }
175
176 private fun getInputXboxButtonKey(key: Int): Int {
177 // The missing xbox buttons are axis
178 return when (key) {
179 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
180 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
181 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
182 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
183 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
184 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
185 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
186 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
187 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
188 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
189 else -> -1
190 }
191 }
192
193 private fun getInputRazerButtonKey(key: Int): Int {
194 // The missing xbox buttons are axis
195 return when (key) {
196 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
197 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
198 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
199 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
200 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
201 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
202 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
203 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
204 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
205 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
206 else -> -1
207 }
208 }
209
210 private fun getInputGenericButtonKey(key: Int): Int {
211 return when (key) {
212 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
213 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
214 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
215 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
216 KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
217 KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
218 KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
219 KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
220 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
221 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
222 KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
223 KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
224 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
225 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
226 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
227 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
228 else -> -1
229 }
230 }
231
232 private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
233 val playerNumber = getPlayerNumber(event.device.controllerNumber)
234
235 when (axis) {
236 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
237 setStickState(
238 playerNumber,
239 NativeLibrary.StickType.STICK_L,
240 event.getAxisValue(MotionEvent.AXIS_X),
241 event.getAxisValue(MotionEvent.AXIS_Y)
242 )
243 MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
244 setStickState(
245 playerNumber,
246 NativeLibrary.StickType.STICK_R,
247 event.getAxisValue(MotionEvent.AXIS_RX),
248 event.getAxisValue(MotionEvent.AXIS_RY)
249 )
250 MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
251 setStickState(
252 playerNumber,
253 NativeLibrary.StickType.STICK_R,
254 event.getAxisValue(MotionEvent.AXIS_Z),
255 event.getAxisValue(MotionEvent.AXIS_RZ)
256 )
257 MotionEvent.AXIS_LTRIGGER ->
258 NativeLibrary.onGamePadButtonEvent(
259 playerNumber,
260 NativeLibrary.ButtonType.TRIGGER_ZL,
261 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_LTRIGGER))
262 )
263 MotionEvent.AXIS_BRAKE ->
264 NativeLibrary.onGamePadButtonEvent(
265 playerNumber,
266 NativeLibrary.ButtonType.TRIGGER_ZL,
267 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
268 )
269 MotionEvent.AXIS_RTRIGGER ->
270 NativeLibrary.onGamePadButtonEvent(
271 playerNumber,
272 NativeLibrary.ButtonType.TRIGGER_ZR,
273 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_RTRIGGER))
274 )
275 MotionEvent.AXIS_GAS ->
276 NativeLibrary.onGamePadButtonEvent(
277 playerNumber,
278 NativeLibrary.ButtonType.TRIGGER_ZR,
279 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
280 )
281 MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
282 setAxisDpadState(
283 playerNumber,
284 event.getAxisValue(MotionEvent.AXIS_HAT_X),
285 event.getAxisValue(MotionEvent.AXIS_HAT_Y)
286 )
287 }
288 }
289
290
291 private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
292 // Joycon support is half dead. Right joystick doesn't work
293 val playerNumber = getPlayerNumber(event.device.controllerNumber)
294
295 when (axis) {
296 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
297 setStickState(
298 playerNumber,
299 NativeLibrary.StickType.STICK_L,
300 event.getAxisValue(MotionEvent.AXIS_X),
301 event.getAxisValue(MotionEvent.AXIS_Y)
302 )
303 MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
304 setStickState(
305 playerNumber,
306 NativeLibrary.StickType.STICK_R,
307 event.getAxisValue(MotionEvent.AXIS_Z),
308 event.getAxisValue(MotionEvent.AXIS_RZ)
309 )
310 MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
311 setStickState(
312 playerNumber,
313 NativeLibrary.StickType.STICK_R,
314 event.getAxisValue(MotionEvent.AXIS_RX),
315 event.getAxisValue(MotionEvent.AXIS_RY)
316 )
317 }
318 }
319
320 private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
321 val playerNumber = getPlayerNumber(event.device.controllerNumber)
322
323 when (axis) {
324 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
325 setStickState(
326 playerNumber,
327 NativeLibrary.StickType.STICK_L,
328 event.getAxisValue(MotionEvent.AXIS_X),
329 event.getAxisValue(MotionEvent.AXIS_Y)
330 )
331 MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
332 setStickState(
333 playerNumber,
334 NativeLibrary.StickType.STICK_R,
335 event.getAxisValue(MotionEvent.AXIS_Z),
336 event.getAxisValue(MotionEvent.AXIS_RZ)
337 )
338 MotionEvent.AXIS_BRAKE ->
339 NativeLibrary.onGamePadButtonEvent(
340 playerNumber,
341 NativeLibrary.ButtonType.TRIGGER_ZL,
342 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
343 )
344 MotionEvent.AXIS_GAS ->
345 NativeLibrary.onGamePadButtonEvent(
346 playerNumber,
347 NativeLibrary.ButtonType.TRIGGER_ZR,
348 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
349 )
350 MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
351 setAxisDpadState(
352 playerNumber,
353 event.getAxisValue(MotionEvent.AXIS_HAT_X),
354 event.getAxisValue(MotionEvent.AXIS_HAT_Y)
355 )
356 }
357 }
358
359
360} \ No newline at end of file
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt
new file mode 100644
index 000000000..19c53c481
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt
@@ -0,0 +1,31 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.annotation.SuppressLint
7import android.app.Activity
8import android.content.Context
9import android.graphics.Rect
10
11object InsetsHelper {
12 const val THREE_BUTTON_NAVIGATION = 0
13 const val TWO_BUTTON_NAVIGATION = 1
14 const val GESTURE_NAVIGATION = 2
15
16 @SuppressLint("DiscouragedApi")
17 fun getSystemGestureType(context: Context): Int {
18 val resources = context.resources
19 val resourceId =
20 resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
21 return if (resourceId != 0) {
22 resources.getInteger(resourceId)
23 } else 0
24 }
25
26 fun getBottomPaddingRequired(activity: Activity): Int {
27 val visibleFrame = Rect()
28 activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame)
29 return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels
30 }
31}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt
new file mode 100644
index 000000000..a193e82a4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt
@@ -0,0 +1,40 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.util.Log
7import org.yuzu.yuzu_emu.BuildConfig
8
9/**
10 * Contains methods that call through to [android.util.Log], but
11 * with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log
12 * levels in release builds.
13 */
14object Log {
15 private const val TAG = "Yuzu Frontend"
16
17 fun verbose(message: String) {
18 if (BuildConfig.DEBUG) {
19 Log.v(TAG, message)
20 }
21 }
22
23 fun debug(message: String) {
24 if (BuildConfig.DEBUG) {
25 Log.d(TAG, message)
26 }
27 }
28
29 fun info(message: String) {
30 Log.i(TAG, message)
31 }
32
33 fun warning(message: String) {
34 Log.w(TAG, message)
35 }
36
37 fun error(message: String) {
38 Log.e(TAG, message)
39 }
40}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt
new file mode 100644
index 000000000..344dd8a0a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt
@@ -0,0 +1,168 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.app.Activity
7import android.app.PendingIntent
8import android.content.Intent
9import android.content.IntentFilter
10import android.nfc.NfcAdapter
11import android.nfc.Tag
12import android.nfc.tech.NfcA
13import android.os.Build
14import android.os.Handler
15import android.os.Looper
16import org.yuzu.yuzu_emu.NativeLibrary
17import java.io.IOException
18
19class NfcReader(private val activity: Activity) {
20 private var nfcAdapter: NfcAdapter? = null
21 private var pendingIntent: PendingIntent? = null
22
23 fun initialize() {
24 nfcAdapter = NfcAdapter.getDefaultAdapter(activity) ?: return
25
26 pendingIntent = PendingIntent.getActivity(
27 activity,
28 0, Intent(activity, activity.javaClass),
29 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
30 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
31 else PendingIntent.FLAG_UPDATE_CURRENT
32 )
33
34 val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
35 tagDetected.addCategory(Intent.CATEGORY_DEFAULT)
36 }
37
38 fun startScanning() {
39 nfcAdapter?.enableForegroundDispatch(activity, pendingIntent, null, null)
40 }
41
42 fun stopScanning() {
43 nfcAdapter?.disableForegroundDispatch(activity)
44 }
45
46 fun onNewIntent(intent: Intent) {
47 val action = intent.action
48 if (NfcAdapter.ACTION_TAG_DISCOVERED != action
49 && NfcAdapter.ACTION_TECH_DISCOVERED != action
50 && NfcAdapter.ACTION_NDEF_DISCOVERED != action
51 ) {
52 return
53 }
54
55 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
56 val tag =
57 intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java) ?: return
58 readTagData(tag)
59 return
60 }
61
62 val tag =
63 intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG) ?: return
64 readTagData(tag)
65 }
66
67 private fun readTagData(tag: Tag) {
68 if (!tag.techList.contains("android.nfc.tech.NfcA")) {
69 return
70 }
71
72 val amiibo = NfcA.get(tag) ?: return
73 amiibo.connect()
74
75 val tagData = ntag215ReadAll(amiibo) ?: return
76 NativeLibrary.onReadNfcTag(tagData)
77
78 nfcAdapter?.ignore(
79 tag,
80 1000,
81 { NativeLibrary.onRemoveNfcTag() },
82 Handler(Looper.getMainLooper())
83 )
84 }
85
86 private fun ntag215ReadAll(amiibo: NfcA): ByteArray? {
87 val bufferSize = amiibo.maxTransceiveLength;
88 val tagSize = 0x21C
89 val pageSize = 4
90 val lastPage = tagSize / pageSize - 1
91 val tagData = ByteArray(tagSize)
92
93 // We need to read the ntag in steps otherwise we overflow the buffer
94 for (i in 0..tagSize step bufferSize - 1) {
95 val dataStart = i / pageSize
96 var dataEnd = (i + bufferSize) / pageSize
97
98 if (dataEnd > lastPage) {
99 dataEnd = lastPage
100 }
101
102 try {
103 val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1)
104 System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize)
105 } catch (e: IOException) {
106 return null;
107 }
108 }
109 return tagData
110 }
111
112 private fun ntag215Read(amiibo: NfcA, page: Int): ByteArray? {
113 return amiibo.transceive(
114 byteArrayOf(
115 0x30.toByte(),
116 (page and 0xFF).toByte()
117 )
118 )
119 }
120
121 private fun ntag215FastRead(amiibo: NfcA, start: Int, end: Int): ByteArray? {
122 return amiibo.transceive(
123 byteArrayOf(
124 0x3A.toByte(),
125 (start and 0xFF).toByte(),
126 (end and 0xFF).toByte()
127 )
128 )
129 }
130
131 private fun ntag215PWrite(
132 amiibo: NfcA,
133 page: Int,
134 data1: Int,
135 data2: Int,
136 data3: Int,
137 data4: Int
138 ): ByteArray? {
139 return amiibo.transceive(
140 byteArrayOf(
141 0xA2.toByte(),
142 (page and 0xFF).toByte(),
143 (data1 and 0xFF).toByte(),
144 (data2 and 0xFF).toByte(),
145 (data3 and 0xFF).toByte(),
146 (data4 and 0xFF).toByte()
147 )
148 )
149 }
150
151 private fun ntag215PwdAuth(
152 amiibo: NfcA,
153 data1: Int,
154 data2: Int,
155 data3: Int,
156 data4: Int
157 ): ByteArray? {
158 return amiibo.transceive(
159 byteArrayOf(
160 0x1B.toByte(),
161 (data1 and 0xFF).toByte(),
162 (data2 and 0xFF).toByte(),
163 (data3 and 0xFF).toByte(),
164 (data4 and 0xFF).toByte()
165 )
166 )
167 }
168}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt
new file mode 100644
index 000000000..87ee7f2e6
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt
@@ -0,0 +1,40 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.content.Intent
7import android.os.Build
8import android.os.Bundle
9import android.os.Parcelable
10import java.io.Serializable
11
12object SerializableHelper {
13 inline fun <reified T : Serializable> Bundle.serializable(key: String): T? {
14 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
15 getSerializable(key, T::class.java)
16 else
17 getSerializable(key) as? T
18 }
19
20 inline fun <reified T : Serializable> Intent.serializable(key: String): T? {
21 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
22 getSerializableExtra(key, T::class.java)
23 else
24 getSerializableExtra(key) as? T
25 }
26
27 inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? {
28 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
29 getParcelable(key, T::class.java)
30 else
31 getParcelable(key) as? T
32 }
33
34 inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? {
35 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
36 getParcelableExtra(key, T::class.java)
37 else
38 getParcelableExtra(key) as? T
39 }
40}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
new file mode 100644
index 000000000..e55767c0f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
@@ -0,0 +1,97 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.app.Activity
7import android.content.res.Configuration
8import android.graphics.Color
9import androidx.annotation.ColorInt
10import androidx.appcompat.app.AppCompatActivity
11import androidx.appcompat.app.AppCompatDelegate
12import androidx.core.content.ContextCompat
13import androidx.core.view.WindowCompat
14import androidx.core.view.WindowInsetsControllerCompat
15import androidx.preference.PreferenceManager
16import org.yuzu.yuzu_emu.R
17import org.yuzu.yuzu_emu.YuzuApplication
18import org.yuzu.yuzu_emu.features.settings.model.Settings
19import org.yuzu.yuzu_emu.ui.main.ThemeProvider
20import kotlin.math.roundToInt
21
22object ThemeHelper {
23 const val SYSTEM_BAR_ALPHA = 0.9f
24
25 private const val DEFAULT = 0
26 private const val MATERIAL_YOU = 1
27
28 fun setTheme(activity: AppCompatActivity) {
29 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
30 setThemeMode(activity)
31 when (preferences.getInt(Settings.PREF_THEME, 0)) {
32 DEFAULT -> activity.setTheme(R.style.Theme_Yuzu_Main)
33 MATERIAL_YOU -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
34 }
35
36 // Using a specific night mode check because this could apply incorrectly when using the
37 // light app mode, dark system mode, and black backgrounds. Launching the settings activity
38 // will then show light mode colors/navigation bars but with black backgrounds.
39 if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
40 && isNightMode(activity)
41 ) {
42 activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
43 }
44 }
45
46 @ColorInt
47 fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int {
48 return Color.argb(
49 (alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color),
50 Color.green(color), Color.blue(color)
51 )
52 }
53
54 fun setCorrectTheme(activity: AppCompatActivity) {
55 val currentTheme = (activity as ThemeProvider).themeId
56 setTheme(activity)
57 if (currentTheme != (activity as ThemeProvider).themeId) {
58 activity.recreate()
59 }
60 }
61
62 fun setThemeMode(activity: AppCompatActivity) {
63 val themeMode = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
64 .getInt(Settings.PREF_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
65 activity.delegate.localNightMode = themeMode
66 val windowController = WindowCompat.getInsetsController(
67 activity.window,
68 activity.window.decorView
69 )
70 when (themeMode) {
71 AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> when (isNightMode(activity)) {
72 false -> setLightModeSystemBars(windowController)
73 true -> setDarkModeSystemBars(windowController)
74 }
75 AppCompatDelegate.MODE_NIGHT_NO -> setLightModeSystemBars(windowController)
76 AppCompatDelegate.MODE_NIGHT_YES -> setDarkModeSystemBars(windowController)
77 }
78 }
79
80 private fun isNightMode(activity: AppCompatActivity): Boolean {
81 return when (activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
82 Configuration.UI_MODE_NIGHT_NO -> false
83 Configuration.UI_MODE_NIGHT_YES -> true
84 else -> false
85 }
86 }
87
88 private fun setLightModeSystemBars(windowController: WindowInsetsControllerCompat) {
89 windowController.isAppearanceLightStatusBars = true
90 windowController.isAppearanceLightNavigationBars = true
91 }
92
93 private fun setDarkModeSystemBars(windowController: WindowInsetsControllerCompat) {
94 windowController.isAppearanceLightStatusBars = false
95 windowController.isAppearanceLightNavigationBars = false
96 }
97}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
new file mode 100644
index 000000000..d89a89983
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.views
5
6import android.content.Context
7import android.util.AttributeSet
8import android.util.Rational
9import android.view.SurfaceView
10import kotlin.math.roundToInt
11
12class FixedRatioSurfaceView @JvmOverloads constructor(
13 context: Context,
14 attrs: AttributeSet? = null,
15 defStyleAttr: Int = 0
16) : SurfaceView(context, attrs, defStyleAttr) {
17 private var aspectRatio: Float = 0f // (width / height), 0f is a special value for stretch
18
19 /**
20 * Sets the desired aspect ratio for this view
21 * @param ratio the ratio to force the view to, or null to stretch to fit
22 */
23 fun setAspectRatio(ratio: Rational?) {
24 aspectRatio = ratio?.toFloat() ?: 0f
25 }
26
27 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
28 super.onMeasure(widthMeasureSpec, heightMeasureSpec)
29 val width = MeasureSpec.getSize(widthMeasureSpec)
30 val height = MeasureSpec.getSize(heightMeasureSpec)
31 if (aspectRatio != 0f) {
32 val newWidth: Int
33 val newHeight: Int
34 if (height * aspectRatio < width) {
35 newWidth = (height * aspectRatio).roundToInt()
36 newHeight = height
37 } else {
38 newWidth = width
39 newHeight = (width / aspectRatio).roundToInt()
40 }
41 val left = (width - newWidth) / 2;
42 val top = (height - newHeight) / 2;
43 setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
44 } else {
45 setLeftTopRightBottom(0, 0, width, height)
46 }
47 }
48}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
new file mode 100644
index 000000000..041781577
--- /dev/null
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -0,0 +1,28 @@
1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4add_library(yuzu-android SHARED
5 android_common/android_common.cpp
6 android_common/android_common.h
7 applets/software_keyboard.cpp
8 applets/software_keyboard.h
9 config.cpp
10 config.h
11 default_ini.h
12 emu_window/emu_window.cpp
13 emu_window/emu_window.h
14 id_cache.cpp
15 id_cache.h
16 native.cpp
17 native.h
18)
19
20set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
21
22target_link_libraries(yuzu-android PRIVATE audio_core common core input_common)
23target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad inih jnigraphics log)
24if (ARCHITECTURE_arm64)
25 target_link_libraries(yuzu-android PRIVATE adrenotools)
26endif()
27
28set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} yuzu-android)
diff --git a/src/android/app/src/main/jni/android_common/android_common.cpp b/src/android/app/src/main/jni/android_common/android_common.cpp
new file mode 100644
index 000000000..52d8ecfeb
--- /dev/null
+++ b/src/android/app/src/main/jni/android_common/android_common.cpp
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "jni/android_common/android_common.h"
5
6#include <string>
7#include <string_view>
8
9#include <jni.h>
10
11#include "common/string_util.h"
12
13std::string GetJString(JNIEnv* env, jstring jstr) {
14 if (!jstr) {
15 return {};
16 }
17
18 const jchar* jchars = env->GetStringChars(jstr, nullptr);
19 const jsize length = env->GetStringLength(jstr);
20 const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length);
21 const std::string converted_string = Common::UTF16ToUTF8(string_view);
22 env->ReleaseStringChars(jstr, jchars);
23
24 return converted_string;
25}
26
27jstring ToJString(JNIEnv* env, std::string_view str) {
28 const std::u16string converted_string = Common::UTF8ToUTF16(str);
29 return env->NewString(reinterpret_cast<const jchar*>(converted_string.data()),
30 static_cast<jint>(converted_string.size()));
31}
32
33jstring ToJString(JNIEnv* env, std::u16string_view str) {
34 return ToJString(env, Common::UTF16ToUTF8(str));
35}
diff --git a/src/android/app/src/main/jni/android_common/android_common.h b/src/android/app/src/main/jni/android_common/android_common.h
new file mode 100644
index 000000000..ccb0c06f7
--- /dev/null
+++ b/src/android/app/src/main/jni/android_common/android_common.h
@@ -0,0 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include <jni.h>
9
10std::string GetJString(JNIEnv* env, jstring jstr);
11jstring ToJString(JNIEnv* env, std::string_view str);
12jstring ToJString(JNIEnv* env, std::u16string_view str);
diff --git a/src/android/app/src/main/jni/applets/software_keyboard.cpp b/src/android/app/src/main/jni/applets/software_keyboard.cpp
new file mode 100644
index 000000000..74e040478
--- /dev/null
+++ b/src/android/app/src/main/jni/applets/software_keyboard.cpp
@@ -0,0 +1,277 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <map>
5#include <thread>
6
7#include <jni.h>
8
9#include "common/logging/log.h"
10#include "common/string_util.h"
11#include "core/core.h"
12#include "jni/android_common/android_common.h"
13#include "jni/applets/software_keyboard.h"
14#include "jni/id_cache.h"
15
16static jclass s_software_keyboard_class;
17static jclass s_keyboard_config_class;
18static jclass s_keyboard_data_class;
19static jmethodID s_swkbd_execute_normal;
20static jmethodID s_swkbd_execute_inline;
21
22namespace SoftwareKeyboard {
23
24static jobject ToJKeyboardParams(const Core::Frontend::KeyboardInitializeParameters& config) {
25 JNIEnv* env = IDCache::GetEnvForThread();
26 jobject object = env->AllocObject(s_keyboard_config_class);
27
28 env->SetObjectField(object,
29 env->GetFieldID(s_keyboard_config_class, "ok_text", "Ljava/lang/String;"),
30 ToJString(env, config.ok_text));
31 env->SetObjectField(
32 object, env->GetFieldID(s_keyboard_config_class, "header_text", "Ljava/lang/String;"),
33 ToJString(env, config.header_text));
34 env->SetObjectField(object,
35 env->GetFieldID(s_keyboard_config_class, "sub_text", "Ljava/lang/String;"),
36 ToJString(env, config.sub_text));
37 env->SetObjectField(
38 object, env->GetFieldID(s_keyboard_config_class, "guide_text", "Ljava/lang/String;"),
39 ToJString(env, config.guide_text));
40 env->SetObjectField(
41 object, env->GetFieldID(s_keyboard_config_class, "initial_text", "Ljava/lang/String;"),
42 ToJString(env, config.initial_text));
43 env->SetShortField(object,
44 env->GetFieldID(s_keyboard_config_class, "left_optional_symbol_key", "S"),
45 static_cast<jshort>(config.left_optional_symbol_key));
46 env->SetShortField(object,
47 env->GetFieldID(s_keyboard_config_class, "right_optional_symbol_key", "S"),
48 static_cast<jshort>(config.right_optional_symbol_key));
49 env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "max_text_length", "I"),
50 static_cast<jint>(config.max_text_length));
51 env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "min_text_length", "I"),
52 static_cast<jint>(config.min_text_length));
53 env->SetIntField(object,
54 env->GetFieldID(s_keyboard_config_class, "initial_cursor_position", "I"),
55 static_cast<jint>(config.initial_cursor_position));
56 env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "type", "I"),
57 static_cast<jint>(config.type));
58 env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "password_mode", "I"),
59 static_cast<jint>(config.password_mode));
60 env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "text_draw_type", "I"),
61 static_cast<jint>(config.text_draw_type));
62 env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "key_disable_flags", "I"),
63 static_cast<jint>(config.key_disable_flags.raw));
64 env->SetBooleanField(object,
65 env->GetFieldID(s_keyboard_config_class, "use_blur_background", "Z"),
66 static_cast<jboolean>(config.use_blur_background));
67 env->SetBooleanField(object,
68 env->GetFieldID(s_keyboard_config_class, "enable_backspace_button", "Z"),
69 static_cast<jboolean>(config.enable_backspace_button));
70 env->SetBooleanField(object,
71 env->GetFieldID(s_keyboard_config_class, "enable_return_button", "Z"),
72 static_cast<jboolean>(config.enable_return_button));
73 env->SetBooleanField(object,
74 env->GetFieldID(s_keyboard_config_class, "disable_cancel_button", "Z"),
75 static_cast<jboolean>(config.disable_cancel_button));
76
77 return object;
78}
79
80AndroidKeyboard::ResultData AndroidKeyboard::ResultData::CreateFromFrontend(jobject object) {
81 JNIEnv* env = IDCache::GetEnvForThread();
82 const jstring string = reinterpret_cast<jstring>(env->GetObjectField(
83 object, env->GetFieldID(s_keyboard_data_class, "text", "Ljava/lang/String;")));
84 return ResultData{GetJString(env, string),
85 static_cast<Service::AM::Applets::SwkbdResult>(env->GetIntField(
86 object, env->GetFieldID(s_keyboard_data_class, "result", "I")))};
87}
88
89AndroidKeyboard::~AndroidKeyboard() = default;
90
91void AndroidKeyboard::InitializeKeyboard(
92 bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters,
93 SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback submit_inline_callback_) {
94 if (is_inline) {
95 LOG_WARNING(
96 Frontend,
97 "(STUBBED) called, backend requested to initialize the inline software keyboard.");
98
99 submit_inline_callback = std::move(submit_inline_callback_);
100 } else {
101 LOG_WARNING(
102 Frontend,
103 "(STUBBED) called, backend requested to initialize the normal software keyboard.");
104
105 submit_normal_callback = std::move(submit_normal_callback_);
106 }
107
108 parameters = std::move(initialize_parameters);
109
110 LOG_INFO(Frontend,
111 "\nKeyboardInitializeParameters:"
112 "\nok_text={}"
113 "\nheader_text={}"
114 "\nsub_text={}"
115 "\nguide_text={}"
116 "\ninitial_text={}"
117 "\nmax_text_length={}"
118 "\nmin_text_length={}"
119 "\ninitial_cursor_position={}"
120 "\ntype={}"
121 "\npassword_mode={}"
122 "\ntext_draw_type={}"
123 "\nkey_disable_flags={}"
124 "\nuse_blur_background={}"
125 "\nenable_backspace_button={}"
126 "\nenable_return_button={}"
127 "\ndisable_cancel_button={}",
128 Common::UTF16ToUTF8(parameters.ok_text), Common::UTF16ToUTF8(parameters.header_text),
129 Common::UTF16ToUTF8(parameters.sub_text), Common::UTF16ToUTF8(parameters.guide_text),
130 Common::UTF16ToUTF8(parameters.initial_text), parameters.max_text_length,
131 parameters.min_text_length, parameters.initial_cursor_position, parameters.type,
132 parameters.password_mode, parameters.text_draw_type, parameters.key_disable_flags.raw,
133 parameters.use_blur_background, parameters.enable_backspace_button,
134 parameters.enable_return_button, parameters.disable_cancel_button);
135}
136
137void AndroidKeyboard::ShowNormalKeyboard() const {
138 LOG_DEBUG(Frontend, "called, backend requested to show the normal software keyboard.");
139
140 ResultData data{};
141
142 // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber.
143 std::thread([&] {
144 data = ResultData::CreateFromFrontend(IDCache::GetEnvForThread()->CallStaticObjectMethod(
145 s_software_keyboard_class, s_swkbd_execute_normal, ToJKeyboardParams(parameters)));
146 }).join();
147
148 SubmitNormalText(data);
149}
150
151void AndroidKeyboard::ShowTextCheckDialog(
152 Service::AM::Applets::SwkbdTextCheckResult text_check_result,
153 std::u16string text_check_message) const {
154 LOG_WARNING(Frontend, "(STUBBED) called, backend requested to show the text check dialog.");
155}
156
157void AndroidKeyboard::ShowInlineKeyboard(
158 Core::Frontend::InlineAppearParameters appear_parameters) const {
159 LOG_WARNING(Frontend,
160 "(STUBBED) called, backend requested to show the inline software keyboard.");
161
162 LOG_INFO(Frontend,
163 "\nInlineAppearParameters:"
164 "\nmax_text_length={}"
165 "\nmin_text_length={}"
166 "\nkey_top_scale_x={}"
167 "\nkey_top_scale_y={}"
168 "\nkey_top_translate_x={}"
169 "\nkey_top_translate_y={}"
170 "\ntype={}"
171 "\nkey_disable_flags={}"
172 "\nkey_top_as_floating={}"
173 "\nenable_backspace_button={}"
174 "\nenable_return_button={}"
175 "\ndisable_cancel_button={}",
176 appear_parameters.max_text_length, appear_parameters.min_text_length,
177 appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y,
178 appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y,
179 appear_parameters.type, appear_parameters.key_disable_flags.raw,
180 appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button,
181 appear_parameters.enable_return_button, appear_parameters.disable_cancel_button);
182
183 // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber.
184 m_is_inline_active = true;
185 std::thread([&] {
186 IDCache::GetEnvForThread()->CallStaticVoidMethod(
187 s_software_keyboard_class, s_swkbd_execute_inline, ToJKeyboardParams(parameters));
188 }).join();
189}
190
191void AndroidKeyboard::HideInlineKeyboard() const {
192 LOG_WARNING(Frontend,
193 "(STUBBED) called, backend requested to hide the inline software keyboard.");
194}
195
196void AndroidKeyboard::InlineTextChanged(
197 Core::Frontend::InlineTextParameters text_parameters) const {
198 LOG_WARNING(Frontend,
199 "(STUBBED) called, backend requested to change the inline keyboard text.");
200
201 LOG_INFO(Frontend,
202 "\nInlineTextParameters:"
203 "\ninput_text={}"
204 "\ncursor_position={}",
205 Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position);
206
207 submit_inline_callback(Service::AM::Applets::SwkbdReplyType::ChangedString,
208 text_parameters.input_text, text_parameters.cursor_position);
209}
210
211void AndroidKeyboard::ExitKeyboard() const {
212 LOG_WARNING(Frontend, "(STUBBED) called, backend requested to exit the software keyboard.");
213}
214
215void AndroidKeyboard::SubmitInlineKeyboardText(std::u16string submitted_text) {
216 if (!m_is_inline_active) {
217 return;
218 }
219
220 m_current_text += submitted_text;
221
222 submit_inline_callback(Service::AM::Applets::SwkbdReplyType::ChangedString, m_current_text,
223 m_current_text.size());
224}
225
226void AndroidKeyboard::SubmitInlineKeyboardInput(int key_code) {
227 static constexpr int KEYCODE_BACK = 4;
228 static constexpr int KEYCODE_ENTER = 66;
229 static constexpr int KEYCODE_DEL = 67;
230
231 if (!m_is_inline_active) {
232 return;
233 }
234
235 switch (key_code) {
236 case KEYCODE_BACK:
237 case KEYCODE_ENTER:
238 m_is_inline_active = false;
239 submit_inline_callback(Service::AM::Applets::SwkbdReplyType::DecidedEnter, m_current_text,
240 static_cast<s32>(m_current_text.size()));
241 break;
242 case KEYCODE_DEL:
243 m_current_text.pop_back();
244 submit_inline_callback(Service::AM::Applets::SwkbdReplyType::ChangedString, m_current_text,
245 m_current_text.size());
246 break;
247 }
248}
249
250void AndroidKeyboard::SubmitNormalText(const ResultData& data) const {
251 submit_normal_callback(data.result, Common::UTF8ToUTF16(data.text), true);
252}
253
254void InitJNI(JNIEnv* env) {
255 s_software_keyboard_class = reinterpret_cast<jclass>(
256 env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard")));
257 s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef(
258 env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig")));
259 s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef(
260 env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardData")));
261
262 s_swkbd_execute_normal = env->GetStaticMethodID(
263 s_software_keyboard_class, "executeNormal",
264 "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/"
265 "applets/keyboard/SoftwareKeyboard$KeyboardData;");
266 s_swkbd_execute_inline = env->GetStaticMethodID(
267 s_software_keyboard_class, "executeInline",
268 "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)V");
269}
270
271void CleanupJNI(JNIEnv* env) {
272 env->DeleteGlobalRef(s_software_keyboard_class);
273 env->DeleteGlobalRef(s_keyboard_config_class);
274 env->DeleteGlobalRef(s_keyboard_data_class);
275}
276
277} // namespace SoftwareKeyboard
diff --git a/src/android/app/src/main/jni/applets/software_keyboard.h b/src/android/app/src/main/jni/applets/software_keyboard.h
new file mode 100644
index 000000000..b2fb59b68
--- /dev/null
+++ b/src/android/app/src/main/jni/applets/software_keyboard.h
@@ -0,0 +1,78 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <jni.h>
7
8#include "core/frontend/applets/software_keyboard.h"
9
10namespace SoftwareKeyboard {
11
12class AndroidKeyboard final : public Core::Frontend::SoftwareKeyboardApplet {
13public:
14 ~AndroidKeyboard() override;
15
16 void Close() const override {
17 ExitKeyboard();
18 }
19
20 void InitializeKeyboard(bool is_inline,
21 Core::Frontend::KeyboardInitializeParameters initialize_parameters,
22 SubmitNormalCallback submit_normal_callback_,
23 SubmitInlineCallback submit_inline_callback_) override;
24
25 void ShowNormalKeyboard() const override;
26
27 void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result,
28 std::u16string text_check_message) const override;
29
30 void ShowInlineKeyboard(
31 Core::Frontend::InlineAppearParameters appear_parameters) const override;
32
33 void HideInlineKeyboard() const override;
34
35 void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override;
36
37 void ExitKeyboard() const override;
38
39 void SubmitInlineKeyboardText(std::u16string submitted_text);
40
41 void SubmitInlineKeyboardInput(int key_code);
42
43private:
44 struct ResultData {
45 static ResultData CreateFromFrontend(jobject object);
46
47 std::string text;
48 Service::AM::Applets::SwkbdResult result{};
49 };
50
51 void SubmitNormalText(const ResultData& result) const;
52
53 Core::Frontend::KeyboardInitializeParameters parameters{};
54
55 mutable SubmitNormalCallback submit_normal_callback;
56 mutable SubmitInlineCallback submit_inline_callback;
57
58private:
59 mutable bool m_is_inline_active{};
60 std::u16string m_current_text;
61};
62
63// Should be called in JNI_Load
64void InitJNI(JNIEnv* env);
65
66// Should be called in JNI_Unload
67void CleanupJNI(JNIEnv* env);
68
69} // namespace SoftwareKeyboard
70
71// Native function calls
72extern "C" {
73JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateFilters(
74 JNIEnv* env, jclass clazz, jstring text);
75
76JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateInput(
77 JNIEnv* env, jclass clazz, jstring text);
78}
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
new file mode 100644
index 000000000..43e8aa72a
--- /dev/null
+++ b/src/android/app/src/main/jni/config.cpp
@@ -0,0 +1,301 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <memory>
5#include <optional>
6#include <sstream>
7
8#include <INIReader.h>
9#include "common/fs/file.h"
10#include "common/fs/fs.h"
11#include "common/fs/path_util.h"
12#include "common/logging/log.h"
13#include "common/settings.h"
14#include "core/hle/service/acc/profile_manager.h"
15#include "input_common/main.h"
16#include "jni/config.h"
17#include "jni/default_ini.h"
18
19namespace FS = Common::FS;
20
21Config::Config(std::optional<std::filesystem::path> config_path)
22 : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")},
23 config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} {
24 Reload();
25}
26
27Config::~Config() = default;
28
29bool Config::LoadINI(const std::string& default_contents, bool retry) {
30 const auto config_loc_str = FS::PathToUTF8String(config_loc);
31 if (config->ParseError() < 0) {
32 if (retry) {
33 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
34 config_loc_str);
35
36 void(FS::CreateParentDir(config_loc));
37 void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents));
38
39 config = std::make_unique<INIReader>(config_loc_str);
40
41 return LoadINI(default_contents, false);
42 }
43 LOG_ERROR(Config, "Failed.");
44 return false;
45 }
46 LOG_INFO(Config, "Successfully loaded {}", config_loc_str);
47 return true;
48}
49
50template <>
51void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
52 std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault());
53 if (setting_value.empty()) {
54 setting_value = setting.GetDefault();
55 }
56 setting = std::move(setting_value);
57}
58
59template <>
60void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
61 setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
62}
63
64template <typename Type, bool ranged>
65void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
66 setting = static_cast<Type>(
67 config->GetInteger(group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
68}
69
70void Config::ReadValues() {
71 ReadSetting("ControlsGeneral", Settings::values.mouse_enabled);
72 ReadSetting("ControlsGeneral", Settings::values.touch_device);
73 ReadSetting("ControlsGeneral", Settings::values.keyboard_enabled);
74 ReadSetting("ControlsGeneral", Settings::values.debug_pad_enabled);
75 ReadSetting("ControlsGeneral", Settings::values.vibration_enabled);
76 ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations);
77 ReadSetting("ControlsGeneral", Settings::values.motion_enabled);
78 Settings::values.touchscreen.enabled =
79 config->GetBoolean("ControlsGeneral", "touch_enabled", true);
80 Settings::values.touchscreen.rotation_angle =
81 config->GetInteger("ControlsGeneral", "touch_angle", 0);
82 Settings::values.touchscreen.diameter_x =
83 config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
84 Settings::values.touchscreen.diameter_y =
85 config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
86
87 int num_touch_from_button_maps =
88 config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
89 if (num_touch_from_button_maps > 0) {
90 for (int i = 0; i < num_touch_from_button_maps; ++i) {
91 Settings::TouchFromButtonMap map;
92 map.name = config->Get("ControlsGeneral",
93 std::string("touch_from_button_maps_") + std::to_string(i) +
94 std::string("_name"),
95 "default");
96 const int num_touch_maps = config->GetInteger(
97 "ControlsGeneral",
98 std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"),
99 0);
100 map.buttons.reserve(num_touch_maps);
101
102 for (int j = 0; j < num_touch_maps; ++j) {
103 std::string touch_mapping =
104 config->Get("ControlsGeneral",
105 std::string("touch_from_button_maps_") + std::to_string(i) +
106 std::string("_bind_") + std::to_string(j),
107 "");
108 map.buttons.emplace_back(std::move(touch_mapping));
109 }
110
111 Settings::values.touch_from_button_maps.emplace_back(std::move(map));
112 }
113 } else {
114 Settings::values.touch_from_button_maps.emplace_back(
115 Settings::TouchFromButtonMap{"default", {}});
116 num_touch_from_button_maps = 1;
117 }
118 Settings::values.touch_from_button_map_index = std::clamp(
119 Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
120
121 ReadSetting("ControlsGeneral", Settings::values.udp_input_servers);
122
123 // Data Storage
124 ReadSetting("Data Storage", Settings::values.use_virtual_sd);
125 FS::SetYuzuPath(FS::YuzuPath::NANDDir,
126 config->Get("Data Storage", "nand_directory",
127 FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
128 FS::SetYuzuPath(FS::YuzuPath::SDMCDir,
129 config->Get("Data Storage", "sdmc_directory",
130 FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
131 FS::SetYuzuPath(FS::YuzuPath::LoadDir,
132 config->Get("Data Storage", "load_directory",
133 FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
134 FS::SetYuzuPath(FS::YuzuPath::DumpDir,
135 config->Get("Data Storage", "dump_directory",
136 FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
137 ReadSetting("Data Storage", Settings::values.gamecard_inserted);
138 ReadSetting("Data Storage", Settings::values.gamecard_current_game);
139 ReadSetting("Data Storage", Settings::values.gamecard_path);
140
141 // System
142 ReadSetting("System", Settings::values.current_user);
143 Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
144 Service::Account::MAX_USERS - 1);
145
146 // Disable docked mode by default on Android
147 Settings::values.use_docked_mode = config->GetBoolean("System", "use_docked_mode", false);
148
149 const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
150 if (rng_seed_enabled) {
151 Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0));
152 } else {
153 Settings::values.rng_seed.SetValue(std::nullopt);
154 }
155
156 const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false);
157 if (custom_rtc_enabled) {
158 Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0);
159 } else {
160 Settings::values.custom_rtc = std::nullopt;
161 }
162
163 ReadSetting("System", Settings::values.language_index);
164 ReadSetting("System", Settings::values.region_index);
165 ReadSetting("System", Settings::values.time_zone_index);
166 ReadSetting("System", Settings::values.sound_index);
167
168 // Core
169 ReadSetting("Core", Settings::values.use_multi_core);
170 ReadSetting("Core", Settings::values.use_unsafe_extended_memory_layout);
171
172 // Cpu
173 ReadSetting("Cpu", Settings::values.cpu_accuracy);
174 ReadSetting("Cpu", Settings::values.cpu_debug_mode);
175 ReadSetting("Cpu", Settings::values.cpuopt_page_tables);
176 ReadSetting("Cpu", Settings::values.cpuopt_block_linking);
177 ReadSetting("Cpu", Settings::values.cpuopt_return_stack_buffer);
178 ReadSetting("Cpu", Settings::values.cpuopt_fast_dispatcher);
179 ReadSetting("Cpu", Settings::values.cpuopt_context_elimination);
180 ReadSetting("Cpu", Settings::values.cpuopt_const_prop);
181 ReadSetting("Cpu", Settings::values.cpuopt_misc_ir);
182 ReadSetting("Cpu", Settings::values.cpuopt_reduce_misalign_checks);
183 ReadSetting("Cpu", Settings::values.cpuopt_fastmem);
184 ReadSetting("Cpu", Settings::values.cpuopt_fastmem_exclusives);
185 ReadSetting("Cpu", Settings::values.cpuopt_recompile_exclusives);
186 ReadSetting("Cpu", Settings::values.cpuopt_ignore_memory_aborts);
187 ReadSetting("Cpu", Settings::values.cpuopt_unsafe_unfuse_fma);
188 ReadSetting("Cpu", Settings::values.cpuopt_unsafe_reduce_fp_error);
189 ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
190 ReadSetting("Cpu", Settings::values.cpuopt_unsafe_inaccurate_nan);
191 ReadSetting("Cpu", Settings::values.cpuopt_unsafe_fastmem_check);
192 ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_global_monitor);
193
194 // Renderer
195 ReadSetting("Renderer", Settings::values.renderer_backend);
196 ReadSetting("Renderer", Settings::values.renderer_debug);
197 ReadSetting("Renderer", Settings::values.renderer_shader_feedback);
198 ReadSetting("Renderer", Settings::values.enable_nsight_aftermath);
199 ReadSetting("Renderer", Settings::values.disable_shader_loop_safety_checks);
200 ReadSetting("Renderer", Settings::values.vulkan_device);
201
202 ReadSetting("Renderer", Settings::values.resolution_setup);
203 ReadSetting("Renderer", Settings::values.scaling_filter);
204 ReadSetting("Renderer", Settings::values.fsr_sharpening_slider);
205 ReadSetting("Renderer", Settings::values.anti_aliasing);
206 ReadSetting("Renderer", Settings::values.fullscreen_mode);
207 ReadSetting("Renderer", Settings::values.aspect_ratio);
208 ReadSetting("Renderer", Settings::values.max_anisotropy);
209 ReadSetting("Renderer", Settings::values.use_speed_limit);
210 ReadSetting("Renderer", Settings::values.speed_limit);
211 ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
212 ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
213 ReadSetting("Renderer", Settings::values.vsync_mode);
214 ReadSetting("Renderer", Settings::values.shader_backend);
215 ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
216 ReadSetting("Renderer", Settings::values.nvdec_emulation);
217 ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
218 ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache);
219
220 ReadSetting("Renderer", Settings::values.bg_red);
221 ReadSetting("Renderer", Settings::values.bg_green);
222 ReadSetting("Renderer", Settings::values.bg_blue);
223
224 // Use GPU accuracy normal by default on Android
225 Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(config->GetInteger(
226 "Renderer", "gpu_accuracy", static_cast<u32>(Settings::GPUAccuracy::Normal)));
227
228 // Use GPU default anisotropic filtering on Android
229 Settings::values.max_anisotropy = config->GetInteger("Renderer", "max_anisotropy", 1);
230
231 // Disable ASTC compute by default on Android
232 Settings::values.accelerate_astc = config->GetBoolean("Renderer", "accelerate_astc", false);
233
234 // Enable asynchronous presentation by default on Android
235 Settings::values.async_presentation =
236 config->GetBoolean("Renderer", "async_presentation", true);
237
238 // Disable force_max_clock by default on Android
239 Settings::values.renderer_force_max_clock =
240 config->GetBoolean("Renderer", "force_max_clock", false);
241
242 // Disable use_reactive_flushing by default on Android
243 Settings::values.use_reactive_flushing =
244 config->GetBoolean("Renderer", "use_reactive_flushing", false);
245
246 // Audio
247 ReadSetting("Audio", Settings::values.sink_id);
248 ReadSetting("Audio", Settings::values.audio_output_device_id);
249 ReadSetting("Audio", Settings::values.volume);
250
251 // Miscellaneous
252 // log_filter has a different default here than from common
253 Settings::values.log_filter = "*:Info";
254 ReadSetting("Miscellaneous", Settings::values.use_dev_keys);
255
256 // Debugging
257 Settings::values.record_frame_times =
258 config->GetBoolean("Debugging", "record_frame_times", false);
259 ReadSetting("Debugging", Settings::values.dump_exefs);
260 ReadSetting("Debugging", Settings::values.dump_nso);
261 ReadSetting("Debugging", Settings::values.enable_fs_access_log);
262 ReadSetting("Debugging", Settings::values.reporting_services);
263 ReadSetting("Debugging", Settings::values.quest_flag);
264 ReadSetting("Debugging", Settings::values.use_debug_asserts);
265 ReadSetting("Debugging", Settings::values.use_auto_stub);
266 ReadSetting("Debugging", Settings::values.disable_macro_jit);
267 ReadSetting("Debugging", Settings::values.disable_macro_hle);
268 ReadSetting("Debugging", Settings::values.use_gdbstub);
269 ReadSetting("Debugging", Settings::values.gdbstub_port);
270
271 const auto title_list = config->Get("AddOns", "title_ids", "");
272 std::stringstream ss(title_list);
273 std::string line;
274 while (std::getline(ss, line, '|')) {
275 const auto title_id = std::stoul(line, nullptr, 16);
276 const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
277
278 std::stringstream inner_ss(disabled_list);
279 std::string inner_line;
280 std::vector<std::string> out;
281 while (std::getline(inner_ss, inner_line, '|')) {
282 out.push_back(inner_line);
283 }
284
285 Settings::values.disabled_addons.insert_or_assign(title_id, out);
286 }
287
288 // Web Service
289 ReadSetting("WebService", Settings::values.enable_telemetry);
290 ReadSetting("WebService", Settings::values.web_api_url);
291 ReadSetting("WebService", Settings::values.yuzu_username);
292 ReadSetting("WebService", Settings::values.yuzu_token);
293
294 // Network
295 ReadSetting("Network", Settings::values.network_interface);
296}
297
298void Config::Reload() {
299 LoadINI(DefaultINI::android_config_file);
300 ReadValues();
301}
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h
new file mode 100644
index 000000000..0d7d6e94d
--- /dev/null
+++ b/src/android/app/src/main/jni/config.h
@@ -0,0 +1,37 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <filesystem>
7#include <memory>
8#include <optional>
9#include <string>
10
11#include "common/settings.h"
12
13class INIReader;
14
15class Config {
16 std::filesystem::path config_loc;
17 std::unique_ptr<INIReader> config;
18
19 bool LoadINI(const std::string& default_contents = "", bool retry = true);
20 void ReadValues();
21
22public:
23 explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt);
24 ~Config();
25
26 void Reload();
27
28private:
29 /**
30 * Applies a value read from the sdl2_config to a Setting.
31 *
32 * @param group The name of the INI group
33 * @param setting The yuzu setting to modify
34 */
35 template <typename Type, bool ranged>
36 void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
37};
diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h
new file mode 100644
index 000000000..d81422a74
--- /dev/null
+++ b/src/android/app/src/main/jni/default_ini.h
@@ -0,0 +1,511 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6namespace DefaultINI {
7
8const char* android_config_file = R"(
9
10[ControlsP0]
11# The input devices and parameters for each Switch native input
12# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ...
13# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
14# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
15
16# Indicates if this player should be connected at boot
17connected=
18
19# for button input, the following devices are available:
20# - "keyboard" (default) for keyboard input. Required parameters:
21# - "code": the code of the key to bind
22# - "sdl" for joystick input using SDL. Required parameters:
23# - "guid": SDL identification GUID of the joystick
24# - "port": the index of the joystick to bind
25# - "button"(optional): the index of the button to bind
26# - "hat"(optional): the index of the hat to bind as direction buttons
27# - "axis"(optional): the index of the axis to bind
28# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
29# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
30# triggered if the axis value crosses
31# - "direction"(only used for axis): "+" means the button is triggered when the axis value
32# is greater than the threshold; "-" means the button is triggered when the axis value
33# is smaller than the threshold
34button_a=
35button_b=
36button_x=
37button_y=
38button_lstick=
39button_rstick=
40button_l=
41button_r=
42button_zl=
43button_zr=
44button_plus=
45button_minus=
46button_dleft=
47button_dup=
48button_dright=
49button_ddown=
50button_lstick_left=
51button_lstick_up=
52button_lstick_right=
53button_lstick_down=
54button_sl=
55button_sr=
56button_home=
57button_screenshot=
58
59# for analog input, the following devices are available:
60# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
61# - "up", "down", "left", "right": sub-devices for each direction.
62# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
63# - "modifier": sub-devices as a modifier.
64# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
65# Must be in range of 0.0-1.0. Defaults to 0.5
66# - "sdl" for joystick input using SDL. Required parameters:
67# - "guid": SDL identification GUID of the joystick
68# - "port": the index of the joystick to bind
69# - "axis_x": the index of the axis to bind as x-axis (default to 0)
70# - "axis_y": the index of the axis to bind as y-axis (default to 1)
71lstick=
72rstick=
73
74# for motion input, the following devices are available:
75# - "keyboard" (default) for emulating random motion input from buttons. Required parameters:
76# - "code": the code of the key to bind
77# - "sdl" for motion input using SDL. Required parameters:
78# - "guid": SDL identification GUID of the joystick
79# - "port": the index of the joystick to bind
80# - "motion": the index of the motion sensor to bind
81# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters:
82# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001"
83# - "port": the port of the cemu hook server
84# - "pad": the index of the joystick
85# - "motion": the index of the motion sensor of the joystick to bind
86motionleft=
87motionright=
88
89[ControlsGeneral]
90# To use the debug_pad, prepend `debug_pad_` before each button setting above.
91# i.e. debug_pad_button_a=
92
93# Enable debug pad inputs to the guest
94# 0 (default): Disabled, 1: Enabled
95debug_pad_enabled =
96
97# Whether to enable or disable vibration
98# 0: Disabled, 1 (default): Enabled
99vibration_enabled=
100
101# Whether to enable or disable accurate vibrations
102# 0 (default): Disabled, 1: Enabled
103enable_accurate_vibrations=
104
105# Enables controller motion inputs
106# 0: Disabled, 1 (default): Enabled
107motion_enabled =
108
109# Defines the udp device's touch screen coordinate system for cemuhookudp devices
110# - "min_x", "min_y", "max_x", "max_y"
111touch_device=
112
113# for mapping buttons to touch inputs.
114#touch_from_button_map=1
115#touch_from_button_maps_0_name=default
116#touch_from_button_maps_0_count=2
117#touch_from_button_maps_0_bind_0=foo
118#touch_from_button_maps_0_bind_1=bar
119# etc.
120
121# List of Cemuhook UDP servers, delimited by ','.
122# Default: 127.0.0.1:26760
123# Example: 127.0.0.1:26760,123.4.5.67:26761
124udp_input_servers =
125
126# Enable controlling an axis via a mouse input.
127# 0 (default): Off, 1: On
128mouse_panning =
129
130# Set mouse sensitivity.
131# Default: 1.0
132mouse_panning_sensitivity =
133
134# Emulate an analog control stick from keyboard inputs.
135# 0 (default): Disabled, 1: Enabled
136emulate_analog_keyboard =
137
138# Enable mouse inputs to the guest
139# 0 (default): Disabled, 1: Enabled
140mouse_enabled =
141
142# Enable keyboard inputs to the guest
143# 0 (default): Disabled, 1: Enabled
144keyboard_enabled =
145
146[Core]
147# Whether to use multi-core for CPU emulation
148# 0: Disabled, 1 (default): Enabled
149use_multi_core =
150
151# Enable unsafe extended guest system memory layout (8GB DRAM)
152# 0 (default): Disabled, 1: Enabled
153use_unsafe_extended_memory_layout =
154
155[Cpu]
156# Adjusts various optimizations.
157# Auto-select mode enables choice unsafe optimizations.
158# Accurate enables only safe optimizations.
159# Unsafe allows any unsafe optimizations.
160# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations
161cpu_accuracy =
162
163# Allow disabling safe optimizations.
164# 0 (default): Disabled, 1: Enabled
165cpu_debug_mode =
166
167# Enable inline page tables optimization (faster guest memory access)
168# 0: Disabled, 1 (default): Enabled
169cpuopt_page_tables =
170
171# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
172# 0: Disabled, 1 (default): Enabled
173cpuopt_block_linking =
174
175# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
176# 0: Disabled, 1 (default): Enabled
177cpuopt_return_stack_buffer =
178
179# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
180# 0: Disabled, 1 (default): Enabled
181cpuopt_fast_dispatcher =
182
183# Enable context elimination CPU Optimization (reduce host memory use for guest context)
184# 0: Disabled, 1 (default): Enabled
185cpuopt_context_elimination =
186
187# Enable constant propagation CPU optimization (basic IR optimization)
188# 0: Disabled, 1 (default): Enabled
189cpuopt_const_prop =
190
191# Enable miscellaneous CPU optimizations (basic IR optimization)
192# 0: Disabled, 1 (default): Enabled
193cpuopt_misc_ir =
194
195# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
196# 0: Disabled, 1 (default): Enabled
197cpuopt_reduce_misalign_checks =
198
199# Enable Host MMU Emulation (faster guest memory access)
200# 0: Disabled, 1 (default): Enabled
201cpuopt_fastmem =
202
203# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access)
204# 0: Disabled, 1 (default): Enabled
205cpuopt_fastmem_exclusives =
206
207# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access)
208# 0: Disabled, 1 (default): Enabled
209cpuopt_recompile_exclusives =
210
211# Enable optimization to ignore invalid memory accesses (faster guest memory access)
212# 0: Disabled, 1 (default): Enabled
213cpuopt_ignore_memory_aborts =
214
215# Enable unfuse FMA (improve performance on CPUs without FMA)
216# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
217# 0: Disabled, 1 (default): Enabled
218cpuopt_unsafe_unfuse_fma =
219
220# Enable faster FRSQRTE and FRECPE
221# Only enabled if cpu_accuracy is set to Unsafe.
222# 0: Disabled, 1 (default): Enabled
223cpuopt_unsafe_reduce_fp_error =
224
225# Enable faster ASIMD instructions (32 bits only)
226# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
227# 0: Disabled, 1 (default): Enabled
228cpuopt_unsafe_ignore_standard_fpcr =
229
230# Enable inaccurate NaN handling
231# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
232# 0: Disabled, 1 (default): Enabled
233cpuopt_unsafe_inaccurate_nan =
234
235# Disable address space checks (64 bits only)
236# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
237# 0: Disabled, 1 (default): Enabled
238cpuopt_unsafe_fastmem_check =
239
240# Enable faster exclusive instructions
241# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
242# 0: Disabled, 1 (default): Enabled
243cpuopt_unsafe_ignore_global_monitor =
244
245[Renderer]
246# Which backend API to use.
247# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null
248backend =
249
250# Whether to enable asynchronous presentation (Vulkan only)
251# 0: Off, 1 (default): On
252async_presentation =
253
254# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).
255# 0 (default): Disabled, 1: Enabled
256force_max_clock =
257
258# Enable graphics API debugging mode.
259# 0 (default): Disabled, 1: Enabled
260debug =
261
262# Enable shader feedback.
263# 0 (default): Disabled, 1: Enabled
264renderer_shader_feedback =
265
266# Enable Nsight Aftermath crash dumps
267# 0 (default): Disabled, 1: Enabled
268nsight_aftermath =
269
270# Disable shader loop safety checks, executing the shader without loop logic changes
271# 0 (default): Disabled, 1: Enabled
272disable_shader_loop_safety_checks =
273
274# Which Vulkan physical device to use (defaults to 0)
275vulkan_device =
276
277# 0: 0.5x (360p/540p) [EXPERIMENTAL]
278# 1: 0.75x (540p/810p) [EXPERIMENTAL]
279# 2 (default): 1x (720p/1080p)
280# 3: 2x (1440p/2160p)
281# 4: 3x (2160p/3240p)
282# 5: 4x (2880p/4320p)
283# 6: 5x (3600p/5400p)
284# 7: 6x (4320p/6480p)
285resolution_setup =
286
287# Pixel filter to use when up- or down-sampling rendered frames.
288# 0: Nearest Neighbor
289# 1 (default): Bilinear
290# 2: Bicubic
291# 3: Gaussian
292# 4: ScaleForce
293# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only]
294scaling_filter =
295
296# Anti-Aliasing (AA)
297# 0 (default): None, 1: FXAA
298anti_aliasing =
299
300# Whether to use fullscreen or borderless window mode
301# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen
302fullscreen_mode =
303
304# Aspect ratio
305# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window
306aspect_ratio =
307
308# Anisotropic filtering
309# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
310max_anisotropy =
311
312# Whether to enable VSync or not.
313# OpenGL: Values other than 0 enable VSync
314# Vulkan: FIFO is selected if the requested mode is not supported by the driver.
315# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
316# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
317# Mailbox can have lower latency than FIFO and does not tear but may drop frames.
318# Immediate (no synchronization) just presents whatever is available and can exhibit tearing.
319# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed
320use_vsync =
321
322# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is
323# not available and GLASM is selected, GLSL will be used.
324# 0: GLSL, 1 (default): GLASM, 2: SPIR-V
325shader_backend =
326
327# Whether to allow asynchronous shader building.
328# 0 (default): Off, 1: On
329use_asynchronous_shaders =
330
331# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
332# 0 (default): Off, 1: On
333use_reactive_flushing =
334
335# NVDEC emulation.
336# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
337nvdec_emulation =
338
339# Accelerate ASTC texture decoding.
340# 0 (default): Off, 1: On
341accelerate_astc =
342
343# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value
344# 0: Off, 1: On (default)
345use_speed_limit =
346
347# Limits the speed of the game to run no faster than this value as a percentage of target speed
348# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
349speed_limit =
350
351# Whether to use disk based shader cache
352# 0: Off, 1 (default): On
353use_disk_shader_cache =
354
355# Which gpu accuracy level to use
356# 0 (default): Normal, 1: High, 2: Extreme (Very slow)
357gpu_accuracy =
358
359# Whether to use asynchronous GPU emulation
360# 0 : Off (slow), 1 (default): On (fast)
361use_asynchronous_gpu_emulation =
362
363# Inform the guest that GPU operations completed more quickly than they did.
364# 0: Off, 1 (default): On
365use_fast_gpu_time =
366
367# Force unmodified buffers to be flushed, which can cost performance.
368# 0: Off (default), 1: On
369use_pessimistic_flushes =
370
371# Whether to use garbage collection or not for GPU caches.
372# 0 (default): Off, 1: On
373use_caches_gc =
374
375# The clear color for the renderer. What shows up on the sides of the bottom screen.
376# Must be in range of 0-255. Defaults to 0 for all.
377bg_red =
378bg_blue =
379bg_green =
380
381[Audio]
382# Which audio output engine to use.
383# auto (default): Auto-select
384# cubeb: Cubeb audio engine (if available)
385# sdl2: SDL2 audio engine (if available)
386# null: No audio output
387output_engine =
388
389# Which audio device to use.
390# auto (default): Auto-select
391output_device =
392
393# Output volume.
394# 100 (default): 100%, 0; mute
395volume =
396
397[Data Storage]
398# Whether to create a virtual SD card.
399# 1: Yes, 0 (default): No
400use_virtual_sd =
401
402# Whether or not to enable gamecard emulation
403# 1: Yes, 0 (default): No
404gamecard_inserted =
405
406# Whether or not the gamecard should be emulated as the current game
407# If 'gamecard_inserted' is 0 this setting is irrelevant
408# 1: Yes, 0 (default): No
409gamecard_current_game =
410
411# Path to an XCI file to use as the gamecard
412# If 'gamecard_inserted' is 0 this setting is irrelevant
413# If 'gamecard_current_game' is 1 this setting is irrelevant
414gamecard_path =
415
416[System]
417# Whether the system is docked
418# 1 (default): Yes, 0: No
419use_docked_mode =
420
421# Sets the seed for the RNG generator built into the switch
422# rng_seed will be ignored and randomly generated if rng_seed_enabled is false
423rng_seed_enabled =
424rng_seed =
425
426# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
427# This will auto-increment, with the time set being the time the game is started
428# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
429custom_rtc_enabled =
430custom_rtc =
431
432# Sets the systems language index
433# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese,
434# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French,
435# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese
436language_index =
437
438# The system region that yuzu will use during emulation
439# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
440region_index =
441
442# The system time zone that yuzu will use during emulation
443# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone
444time_zone_index =
445
446# Sets the sound output mode.
447# 0: Mono, 1 (default): Stereo, 2: Surround
448sound_index =
449
450[Miscellaneous]
451# A filter which removes logs below a certain logging level.
452# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
453log_filter = *:Trace
454
455# Use developer keys
456# 0 (default): Disabled, 1: Enabled
457use_dev_keys =
458
459[Debugging]
460# Record frame time data, can be found in the log directory. Boolean value
461record_frame_times =
462# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
463dump_exefs=false
464# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
465dump_nso=false
466# Determines whether or not yuzu will save the filesystem access log.
467enable_fs_access_log=false
468# Enables verbose reporting services
469reporting_services =
470# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode
471# false: Retail/Normal Mode (default), true: Kiosk Mode
472quest_flag =
473# Determines whether debug asserts should be enabled, which will throw an exception on asserts.
474# false: Disabled (default), true: Enabled
475use_debug_asserts =
476# Determines whether unimplemented HLE service calls should be automatically stubbed.
477# false: Disabled (default), true: Enabled
478use_auto_stub =
479# Enables/Disables the macro JIT compiler
480disable_macro_jit=false
481# Determines whether to enable the GDB stub and wait for the debugger to attach before running.
482# false: Disabled (default), true: Enabled
483use_gdbstub=false
484# The port to use for the GDB server, if it is enabled.
485gdbstub_port=6543
486
487[WebService]
488# Whether or not to enable telemetry
489# 0: No, 1 (default): Yes
490enable_telemetry =
491# URL for Web API
492web_api_url = https://api.yuzu-emu.org
493# Username and token for yuzu Web Service
494# See https://profile.yuzu-emu.org/ for more info
495yuzu_username =
496yuzu_token =
497
498[Network]
499# Name of the network interface device to use with yuzu LAN play.
500# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo'
501# e.g. On Windows: 'Ethernet', 'Wi-Fi'
502network_interface =
503
504[AddOns]
505# Used to disable add-ons
506# List of title IDs of games that will have add-ons disabled (separated by '|'):
507title_ids =
508# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
509# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
510)";
511} // namespace DefaultINI
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp
new file mode 100644
index 000000000..a890c6604
--- /dev/null
+++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include <android/native_window_jni.h>
5
6#include "common/logging/log.h"
7#include "input_common/drivers/touch_screen.h"
8#include "input_common/drivers/virtual_amiibo.h"
9#include "input_common/drivers/virtual_gamepad.h"
10#include "input_common/main.h"
11#include "jni/emu_window/emu_window.h"
12
13void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
14 window_info.render_surface = reinterpret_cast<void*>(surface);
15}
16
17void EmuWindow_Android::OnTouchPressed(int id, float x, float y) {
18 const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
19 m_input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id);
20}
21
22void EmuWindow_Android::OnTouchMoved(int id, float x, float y) {
23 const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
24 m_input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id);
25}
26
27void EmuWindow_Android::OnTouchReleased(int id) {
28 m_input_subsystem->GetTouchScreen()->TouchReleased(id);
29}
30
31void EmuWindow_Android::OnGamepadButtonEvent(int player_index, int button_id, bool pressed) {
32 m_input_subsystem->GetVirtualGamepad()->SetButtonState(player_index, button_id, pressed);
33}
34
35void EmuWindow_Android::OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y) {
36 m_input_subsystem->GetVirtualGamepad()->SetStickPosition(player_index, stick_id, x, y);
37}
38
39void EmuWindow_Android::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x,
40 float gyro_y, float gyro_z, float accel_x,
41 float accel_y, float accel_z) {
42 m_input_subsystem->GetVirtualGamepad()->SetMotionState(
43 player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
44}
45
46void EmuWindow_Android::OnReadNfcTag(std::span<u8> data) {
47 m_input_subsystem->GetVirtualAmiibo()->LoadAmiibo(data);
48}
49
50void EmuWindow_Android::OnRemoveNfcTag() {
51 m_input_subsystem->GetVirtualAmiibo()->CloseAmiibo();
52}
53
54EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem,
55 ANativeWindow* surface,
56 std::shared_ptr<Common::DynamicLibrary> driver_library)
57 : m_input_subsystem{input_subsystem}, m_driver_library{driver_library} {
58 LOG_INFO(Frontend, "initializing");
59
60 if (!surface) {
61 LOG_CRITICAL(Frontend, "surface is nullptr");
62 return;
63 }
64
65 m_window_width = ANativeWindow_getWidth(surface);
66 m_window_height = ANativeWindow_getHeight(surface);
67
68 // Ensures that we emulate with the correct aspect ratio.
69 UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
70
71 window_info.type = Core::Frontend::WindowSystemType::Android;
72 window_info.render_surface = reinterpret_cast<void*>(surface);
73
74 m_input_subsystem->Initialize();
75}
76
77EmuWindow_Android::~EmuWindow_Android() {
78 m_input_subsystem->Shutdown();
79}
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h
new file mode 100644
index 000000000..b38087f73
--- /dev/null
+++ b/src/android/app/src/main/jni/emu_window/emu_window.h
@@ -0,0 +1,64 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <span>
8
9#include "core/frontend/emu_window.h"
10#include "core/frontend/graphics_context.h"
11#include "input_common/main.h"
12
13struct ANativeWindow;
14
15class GraphicsContext_Android final : public Core::Frontend::GraphicsContext {
16public:
17 explicit GraphicsContext_Android(std::shared_ptr<Common::DynamicLibrary> driver_library)
18 : m_driver_library{driver_library} {}
19
20 ~GraphicsContext_Android() = default;
21
22 std::shared_ptr<Common::DynamicLibrary> GetDriverLibrary() override {
23 return m_driver_library;
24 }
25
26private:
27 std::shared_ptr<Common::DynamicLibrary> m_driver_library;
28};
29
30class EmuWindow_Android final : public Core::Frontend::EmuWindow {
31
32public:
33 EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem, ANativeWindow* surface,
34 std::shared_ptr<Common::DynamicLibrary> driver_library);
35
36 ~EmuWindow_Android();
37
38 void OnSurfaceChanged(ANativeWindow* surface);
39 void OnTouchPressed(int id, float x, float y);
40 void OnTouchMoved(int id, float x, float y);
41 void OnTouchReleased(int id);
42 void OnGamepadButtonEvent(int player_index, int button_id, bool pressed);
43 void OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y);
44 void OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
45 float gyro_z, float accel_x, float accel_y, float accel_z);
46 void OnReadNfcTag(std::span<u8> data);
47 void OnRemoveNfcTag();
48 void OnFrameDisplayed() override {}
49
50 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
51 return {std::make_unique<GraphicsContext_Android>(m_driver_library)};
52 }
53 bool IsShown() const override {
54 return true;
55 };
56
57private:
58 InputCommon::InputSubsystem* m_input_subsystem{};
59
60 float m_window_width{};
61 float m_window_height{};
62
63 std::shared_ptr<Common::DynamicLibrary> m_driver_library;
64};
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
new file mode 100644
index 000000000..9cbbf23a3
--- /dev/null
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -0,0 +1,116 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <jni.h>
5
6#include "common/assert.h"
7#include "common/fs/fs_android.h"
8#include "jni/applets/software_keyboard.h"
9#include "jni/id_cache.h"
10#include "video_core/rasterizer_interface.h"
11
12static JavaVM* s_java_vm;
13static jclass s_native_library_class;
14static jclass s_disk_cache_progress_class;
15static jclass s_load_callback_stage_class;
16static jmethodID s_exit_emulation_activity;
17static jmethodID s_disk_cache_load_progress;
18
19static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
20
21namespace IDCache {
22
23JNIEnv* GetEnvForThread() {
24 thread_local static struct OwnedEnv {
25 OwnedEnv() {
26 status = s_java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
27 if (status == JNI_EDETACHED)
28 s_java_vm->AttachCurrentThread(&env, nullptr);
29 }
30
31 ~OwnedEnv() {
32 if (status == JNI_EDETACHED)
33 s_java_vm->DetachCurrentThread();
34 }
35
36 int status;
37 JNIEnv* env = nullptr;
38 } owned;
39 return owned.env;
40}
41
42jclass GetNativeLibraryClass() {
43 return s_native_library_class;
44}
45
46jclass GetDiskCacheProgressClass() {
47 return s_disk_cache_progress_class;
48}
49
50jclass GetDiskCacheLoadCallbackStageClass() {
51 return s_load_callback_stage_class;
52}
53
54jmethodID GetExitEmulationActivity() {
55 return s_exit_emulation_activity;
56}
57
58jmethodID GetDiskCacheLoadProgress() {
59 return s_disk_cache_load_progress;
60}
61
62} // namespace IDCache
63
64#ifdef __cplusplus
65extern "C" {
66#endif
67
68jint JNI_OnLoad(JavaVM* vm, void* reserved) {
69 s_java_vm = vm;
70
71 JNIEnv* env;
72 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK)
73 return JNI_ERR;
74
75 // Initialize Java classes
76 const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary");
77 s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
78 s_disk_cache_progress_class = reinterpret_cast<jclass>(env->NewGlobalRef(
79 env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress")));
80 s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
81 "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
82
83 // Initialize methods
84 s_exit_emulation_activity =
85 env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
86 s_disk_cache_load_progress =
87 env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
88
89 // Initialize Android Storage
90 Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
91
92 // Initialize applets
93 SoftwareKeyboard::InitJNI(env);
94
95 return JNI_VERSION;
96}
97
98void JNI_OnUnload(JavaVM* vm, void* reserved) {
99 JNIEnv* env;
100 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK) {
101 return;
102 }
103
104 // UnInitialize Android Storage
105 Common::FS::Android::UnRegisterCallbacks();
106 env->DeleteGlobalRef(s_native_library_class);
107 env->DeleteGlobalRef(s_disk_cache_progress_class);
108 env->DeleteGlobalRef(s_load_callback_stage_class);
109
110 // UnInitialize applets
111 SoftwareKeyboard::CleanupJNI(env);
112}
113
114#ifdef __cplusplus
115}
116#endif
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
new file mode 100644
index 000000000..be535fe1e
--- /dev/null
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -0,0 +1,19 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <jni.h>
7
8#include "video_core/rasterizer_interface.h"
9
10namespace IDCache {
11
12JNIEnv* GetEnvForThread();
13jclass GetNativeLibraryClass();
14jclass GetDiskCacheProgressClass();
15jclass GetDiskCacheLoadCallbackStageClass();
16jmethodID GetExitEmulationActivity();
17jmethodID GetDiskCacheLoadProgress();
18
19} // namespace IDCache
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
new file mode 100644
index 000000000..4091c23d1
--- /dev/null
+++ b/src/android/app/src/main/jni/native.cpp
@@ -0,0 +1,850 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <codecvt>
5#include <locale>
6#include <string>
7#include <string_view>
8#include <dlfcn.h>
9
10#ifdef ARCHITECTURE_arm64
11#include <adrenotools/driver.h>
12#endif
13
14#include <android/api-level.h>
15#include <android/native_window_jni.h>
16#include <core/loader/nro.h>
17
18#include "common/detached_tasks.h"
19#include "common/dynamic_library.h"
20#include "common/fs/path_util.h"
21#include "common/logging/backend.h"
22#include "common/logging/log.h"
23#include "common/microprofile.h"
24#include "common/scm_rev.h"
25#include "common/scope_exit.h"
26#include "common/settings.h"
27#include "common/string_util.h"
28#include "core/core.h"
29#include "core/cpu_manager.h"
30#include "core/crypto/key_manager.h"
31#include "core/file_sys/card_image.h"
32#include "core/file_sys/registered_cache.h"
33#include "core/file_sys/submission_package.h"
34#include "core/file_sys/vfs.h"
35#include "core/file_sys/vfs_real.h"
36#include "core/frontend/applets/cabinet.h"
37#include "core/frontend/applets/controller.h"
38#include "core/frontend/applets/error.h"
39#include "core/frontend/applets/general_frontend.h"
40#include "core/frontend/applets/mii_edit.h"
41#include "core/frontend/applets/profile_select.h"
42#include "core/frontend/applets/software_keyboard.h"
43#include "core/frontend/applets/web_browser.h"
44#include "core/hid/emulated_controller.h"
45#include "core/hid/hid_core.h"
46#include "core/hid/hid_types.h"
47#include "core/hle/service/acc/profile_manager.h"
48#include "core/hle/service/am/applet_ae.h"
49#include "core/hle/service/am/applet_oe.h"
50#include "core/hle/service/am/applets/applets.h"
51#include "core/hle/service/filesystem/filesystem.h"
52#include "core/loader/loader.h"
53#include "core/perf_stats.h"
54#include "jni/android_common/android_common.h"
55#include "jni/applets/software_keyboard.h"
56#include "jni/config.h"
57#include "jni/emu_window/emu_window.h"
58#include "jni/id_cache.h"
59#include "video_core/rasterizer_interface.h"
60#include "video_core/renderer_base.h"
61
62namespace {
63
64class EmulationSession final {
65public:
66 EmulationSession() {
67 m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
68 }
69
70 ~EmulationSession() = default;
71
72 static EmulationSession& GetInstance() {
73 return s_instance;
74 }
75
76 const Core::System& System() const {
77 return m_system;
78 }
79
80 Core::System& System() {
81 return m_system;
82 }
83
84 const EmuWindow_Android& Window() const {
85 return *m_window;
86 }
87
88 EmuWindow_Android& Window() {
89 return *m_window;
90 }
91
92 ANativeWindow* NativeWindow() const {
93 return m_native_window;
94 }
95
96 void SetNativeWindow(ANativeWindow* native_window) {
97 m_native_window = native_window;
98 }
99
100 int InstallFileToNand(std::string filename) {
101 const auto copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
102 std::size_t block_size) {
103 if (src == nullptr || dest == nullptr) {
104 return false;
105 }
106 if (!dest->Resize(src->GetSize())) {
107 return false;
108 }
109
110 using namespace Common::Literals;
111 std::vector<u8> buffer(1_MiB);
112
113 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
114 const auto read = src->Read(buffer.data(), buffer.size(), i);
115 dest->Write(buffer.data(), read, i);
116 }
117 return true;
118 };
119
120 enum InstallResult {
121 Success = 0,
122 SuccessFileOverwritten = 1,
123 InstallError = 2,
124 ErrorBaseGame = 3,
125 ErrorFilenameExtension = 4,
126 };
127
128 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
129 m_system.GetFileSystemController().CreateFactories(*m_vfs);
130
131 std::shared_ptr<FileSys::NSP> nsp;
132 if (filename.ends_with("nsp")) {
133 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
134 if (nsp->IsExtractedType()) {
135 return InstallError;
136 }
137 } else if (filename.ends_with("xci")) {
138 const auto xci =
139 std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
140 nsp = xci->GetSecurePartitionNSP();
141 } else {
142 return ErrorFilenameExtension;
143 }
144
145 if (!nsp) {
146 return InstallError;
147 }
148
149 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
150 return InstallError;
151 }
152
153 const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
154 *nsp, true, copy_func);
155
156 switch (res) {
157 case FileSys::InstallResult::Success:
158 return Success;
159 case FileSys::InstallResult::OverwriteExisting:
160 return SuccessFileOverwritten;
161 case FileSys::InstallResult::ErrorBaseInstall:
162 return ErrorBaseGame;
163 default:
164 return InstallError;
165 }
166 }
167
168 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
169 const std::string& custom_driver_name,
170 const std::string& file_redirect_dir) {
171#ifdef ARCHITECTURE_arm64
172 void* handle{};
173 const char* file_redirect_dir_{};
174 int featureFlags{};
175
176 // Enable driver file redirection when renderer debugging is enabled.
177 if (Settings::values.renderer_debug && file_redirect_dir.size()) {
178 featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
179 file_redirect_dir_ = file_redirect_dir.c_str();
180 }
181
182 // Try to load a custom driver.
183 if (custom_driver_name.size()) {
184 handle = adrenotools_open_libvulkan(
185 RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
186 custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
187 }
188
189 // Try to load the system driver.
190 if (!handle) {
191 handle =
192 adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
193 nullptr, nullptr, file_redirect_dir_, nullptr);
194 }
195
196 m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle);
197#endif
198 }
199
200 bool IsRunning() const {
201 std::scoped_lock lock(m_mutex);
202 return m_is_running;
203 }
204
205 const Core::PerfStatsResults& PerfStats() const {
206 std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
207 return m_perf_stats;
208 }
209
210 void SurfaceChanged() {
211 if (!IsRunning()) {
212 return;
213 }
214 m_window->OnSurfaceChanged(m_native_window);
215 m_system.Renderer().NotifySurfaceChanged();
216 }
217
218 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
219 std::scoped_lock lock(m_mutex);
220
221 // Loads the configuration.
222 Config{};
223
224 // Create the render window.
225 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
226 m_vulkan_library);
227
228 m_system.SetFilesystem(m_vfs);
229
230 // Initialize system.
231 auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
232 m_software_keyboard = android_keyboard.get();
233 m_system.SetShuttingDown(false);
234 m_system.ApplySettings();
235 m_system.HIDCore().ReloadInputDevices();
236 m_system.SetAppletFrontendSet({
237 nullptr, // Amiibo Settings
238 nullptr, // Controller Selector
239 nullptr, // Error Display
240 nullptr, // Mii Editor
241 nullptr, // Parental Controls
242 nullptr, // Photo Viewer
243 nullptr, // Profile Selector
244 std::move(android_keyboard), // Software Keyboard
245 nullptr, // Web Browser
246 });
247 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
248 m_system.GetFileSystemController().CreateFactories(*m_vfs);
249
250 // Initialize account manager
251 m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
252
253 // Load the ROM.
254 m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
255 if (m_load_result != Core::SystemResultStatus::Success) {
256 return m_load_result;
257 }
258
259 // Complete initialization.
260 m_system.GPU().Start();
261 m_system.GetCpuManager().OnGpuReady();
262 m_system.RegisterExitCallback([&] { HaltEmulation(); });
263
264 return Core::SystemResultStatus::Success;
265 }
266
267 void ShutdownEmulation() {
268 std::scoped_lock lock(m_mutex);
269
270 m_is_running = false;
271
272 // Unload user input.
273 m_system.HIDCore().UnloadInputDevices();
274
275 // Shutdown the main emulated process
276 if (m_load_result == Core::SystemResultStatus::Success) {
277 m_system.DetachDebugger();
278 m_system.ShutdownMainProcess();
279 m_detached_tasks.WaitForAllTasks();
280 m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
281 }
282
283 // Tear down the render window.
284 m_window.reset();
285 }
286
287 void PauseEmulation() {
288 std::scoped_lock lock(m_mutex);
289 m_system.Pause();
290 }
291
292 void UnPauseEmulation() {
293 std::scoped_lock lock(m_mutex);
294 m_system.Run();
295 }
296
297 void HaltEmulation() {
298 std::scoped_lock lock(m_mutex);
299 m_is_running = false;
300 m_cv.notify_one();
301 }
302
303 void RunEmulation() {
304 {
305 std::scoped_lock lock(m_mutex);
306 m_is_running = true;
307 }
308
309 // Load the disk shader cache.
310 if (Settings::values.use_disk_shader_cache.GetValue()) {
311 LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
312 m_system.Renderer().ReadRasterizer()->LoadDiskResources(
313 m_system.GetApplicationProcessProgramID(), std::stop_token{},
314 LoadDiskCacheProgress);
315 LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
316 }
317
318 void(m_system.Run());
319
320 if (m_system.DebuggerEnabled()) {
321 m_system.InitializeDebugger();
322 }
323
324 while (true) {
325 {
326 std::unique_lock lock(m_mutex);
327 if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
328 [&]() { return !m_is_running; })) {
329 // Emulation halted.
330 break;
331 }
332 }
333 {
334 // Refresh performance stats.
335 std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
336 m_perf_stats = m_system.GetAndResetPerfStats();
337 }
338 }
339 }
340
341 std::string GetRomTitle(const std::string& path) {
342 return GetRomMetadata(path).title;
343 }
344
345 std::vector<u8> GetRomIcon(const std::string& path) {
346 return GetRomMetadata(path).icon;
347 }
348
349 bool GetIsHomebrew(const std::string& path) {
350 return GetRomMetadata(path).isHomebrew;
351 }
352
353 void ResetRomMetadata() {
354 m_rom_metadata_cache.clear();
355 }
356
357 bool IsHandheldOnly() {
358 const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
359
360 if (npad_style_set.fullkey == 1) {
361 return false;
362 }
363
364 if (npad_style_set.handheld == 0) {
365 return false;
366 }
367
368 return !Settings::values.use_docked_mode.GetValue();
369 }
370
371 void SetDeviceType(int index, int type) {
372 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
373 controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
374 }
375
376 void OnGamepadConnectEvent(int index) {
377 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
378
379 // Ensure that player1 is configured correctly and handheld disconnected
380 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
381 auto handheld =
382 m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
383
384 if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
385 handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
386 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
387 handheld->Disconnect();
388 }
389 }
390
391 // Ensure that handheld is configured correctly and player 1 disconnected
392 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
393 auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
394
395 if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
396 player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
397 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
398 player1->Disconnect();
399 }
400 }
401
402 if (!controller->IsConnected()) {
403 controller->Connect();
404 }
405 }
406
407 void OnGamepadDisconnectEvent(int index) {
408 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
409 controller->Disconnect();
410 }
411
412 SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() {
413 return m_software_keyboard;
414 }
415
416private:
417 struct RomMetadata {
418 std::string title;
419 std::vector<u8> icon;
420 bool isHomebrew;
421 };
422
423 RomMetadata GetRomMetadata(const std::string& path) {
424 if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
425 return search->second;
426 }
427
428 return CacheRomMetadata(path);
429 }
430
431 RomMetadata CacheRomMetadata(const std::string& path) {
432 const auto file = Core::GetGameFileFromPath(m_vfs, path);
433 auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
434
435 RomMetadata entry;
436 loader->ReadTitle(entry.title);
437 loader->ReadIcon(entry.icon);
438 if (loader->GetFileType() == Loader::FileType::NRO) {
439 auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
440 entry.isHomebrew = loader_nro->IsHomebrew();
441 } else {
442 entry.isHomebrew = false;
443 }
444
445 m_rom_metadata_cache[path] = entry;
446
447 return entry;
448 }
449
450private:
451 static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
452 JNIEnv* env = IDCache::GetEnvForThread();
453 env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
454 IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
455 static_cast<jint>(progress), static_cast<jint>(max));
456 }
457
458private:
459 static EmulationSession s_instance;
460
461 // Frontend management
462 std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
463
464 // Window management
465 std::unique_ptr<EmuWindow_Android> m_window;
466 ANativeWindow* m_native_window{};
467
468 // Core emulation
469 Core::System m_system;
470 InputCommon::InputSubsystem m_input_subsystem;
471 Common::DetachedTasks m_detached_tasks;
472 Core::PerfStatsResults m_perf_stats{};
473 std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
474 Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
475 bool m_is_running{};
476 SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
477 std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
478
479 // GPU driver parameters
480 std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
481
482 // Synchronization
483 std::condition_variable_any m_cv;
484 mutable std::mutex m_perf_stats_mutex;
485 mutable std::mutex m_mutex;
486};
487
488/*static*/ EmulationSession EmulationSession::s_instance;
489
490} // Anonymous namespace
491
492static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
493 Common::Log::Initialize();
494 Common::Log::SetColorConsoleBackendEnabled(true);
495 Common::Log::Start();
496
497 MicroProfileOnThreadCreate("EmuThread");
498 SCOPE_EXIT({ MicroProfileShutdown(); });
499
500 LOG_INFO(Frontend, "starting");
501
502 if (filepath.empty()) {
503 LOG_CRITICAL(Frontend, "failed to load: filepath empty!");
504 return Core::SystemResultStatus::ErrorLoader;
505 }
506
507 SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
508
509 const auto result = EmulationSession::GetInstance().InitializeEmulation(filepath);
510 if (result != Core::SystemResultStatus::Success) {
511 return result;
512 }
513
514 EmulationSession::GetInstance().RunEmulation();
515
516 return Core::SystemResultStatus::Success;
517}
518
519extern "C" {
520
521void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
522 [[maybe_unused]] jclass clazz,
523 jobject surf) {
524 EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf));
525 EmulationSession::GetInstance().SurfaceChanged();
526}
527
528void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
529 [[maybe_unused]] jclass clazz) {
530 ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
531 EmulationSession::GetInstance().SetNativeWindow(nullptr);
532 EmulationSession::GetInstance().SurfaceChanged();
533}
534
535void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
536 [[maybe_unused]] jclass clazz,
537 jstring j_directory) {
538 Common::FS::SetAppDirectory(GetJString(env, j_directory));
539}
540
541int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env,
542 [[maybe_unused]] jclass clazz,
543 jstring j_file) {
544 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
545}
546
547void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
548 JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
549 jstring custom_driver_name, jstring file_redirect_dir) {
550 EmulationSession::GetInstance().InitializeGpuDriver(
551 GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir),
552 GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
553}
554
555jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
556 [[maybe_unused]] jclass clazz) {
557 Core::Crypto::KeyManager::Instance().ReloadKeys();
558 return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
559}
560
561void Java_org_yuzu_yuzu_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv* env,
562 [[maybe_unused]] jclass clazz) {
563 EmulationSession::GetInstance().UnPauseEmulation();
564}
565
566void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env,
567 [[maybe_unused]] jclass clazz) {
568 EmulationSession::GetInstance().PauseEmulation();
569}
570
571void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env,
572 [[maybe_unused]] jclass clazz) {
573 EmulationSession::GetInstance().HaltEmulation();
574}
575
576void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata([[maybe_unused]] JNIEnv* env,
577 [[maybe_unused]] jclass clazz) {
578 EmulationSession::GetInstance().ResetRomMetadata();
579}
580
581jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv* env,
582 [[maybe_unused]] jclass clazz) {
583 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
584}
585
586jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
587 [[maybe_unused]] jclass clazz) {
588 return EmulationSession::GetInstance().IsHandheldOnly();
589}
590
591jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env,
592 [[maybe_unused]] jclass clazz,
593 jint j_device, jint j_type) {
594 if (EmulationSession::GetInstance().IsRunning()) {
595 EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
596 }
597 return static_cast<jboolean>(true);
598}
599
600jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env,
601 [[maybe_unused]] jclass clazz,
602 jint j_device) {
603 if (EmulationSession::GetInstance().IsRunning()) {
604 EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
605 }
606 return static_cast<jboolean>(true);
607}
608
609jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
610 [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device) {
611 if (EmulationSession::GetInstance().IsRunning()) {
612 EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
613 }
614 return static_cast<jboolean>(true);
615}
616jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env,
617 [[maybe_unused]] jclass clazz,
618 [[maybe_unused]] jint j_device,
619 jint j_button, jint action) {
620 if (EmulationSession::GetInstance().IsRunning()) {
621 // Ensure gamepad is connected
622 EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
623 EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button,
624 action != 0);
625 }
626 return static_cast<jboolean>(true);
627}
628
629jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_unused]] JNIEnv* env,
630 [[maybe_unused]] jclass clazz,
631 jint j_device, jint stick_id,
632 jfloat x, jfloat y) {
633 if (EmulationSession::GetInstance().IsRunning()) {
634 EmulationSession::GetInstance().Window().OnGamepadJoystickEvent(j_device, stick_id, x, y);
635 }
636 return static_cast<jboolean>(true);
637}
638
639jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
640 [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device,
641 jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y, jfloat gyro_z, jfloat accel_x,
642 jfloat accel_y, jfloat accel_z) {
643 if (EmulationSession::GetInstance().IsRunning()) {
644 EmulationSession::GetInstance().Window().OnGamepadMotionEvent(
645 j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
646 }
647 return static_cast<jboolean>(true);
648}
649
650jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNIEnv* env,
651 [[maybe_unused]] jclass clazz,
652 jbyteArray j_data) {
653 jboolean isCopy{false};
654 std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
655 static_cast<size_t>(env->GetArrayLength(j_data)));
656
657 if (EmulationSession::GetInstance().IsRunning()) {
658 EmulationSession::GetInstance().Window().OnReadNfcTag(data);
659 }
660 return static_cast<jboolean>(true);
661}
662
663jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag([[maybe_unused]] JNIEnv* env,
664 [[maybe_unused]] jclass clazz) {
665 if (EmulationSession::GetInstance().IsRunning()) {
666 EmulationSession::GetInstance().Window().OnRemoveNfcTag();
667 }
668 return static_cast<jboolean>(true);
669}
670
671void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed([[maybe_unused]] JNIEnv* env,
672 [[maybe_unused]] jclass clazz, jint id,
673 jfloat x, jfloat y) {
674 if (EmulationSession::GetInstance().IsRunning()) {
675 EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y);
676 }
677}
678
679void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* env,
680 [[maybe_unused]] jclass clazz, jint id,
681 jfloat x, jfloat y) {
682 if (EmulationSession::GetInstance().IsRunning()) {
683 EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y);
684 }
685}
686
687void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased([[maybe_unused]] JNIEnv* env,
688 [[maybe_unused]] jclass clazz, jint id) {
689 if (EmulationSession::GetInstance().IsRunning()) {
690 EmulationSession::GetInstance().Window().OnTouchReleased(id);
691 }
692}
693
694jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon([[maybe_unused]] JNIEnv* env,
695 [[maybe_unused]] jclass clazz,
696 [[maybe_unused]] jstring j_filename) {
697 auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
698 jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
699 env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
700 reinterpret_cast<jbyte*>(icon_data.data()));
701 return icon;
702}
703
704jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle([[maybe_unused]] JNIEnv* env,
705 [[maybe_unused]] jclass clazz,
706 [[maybe_unused]] jstring j_filename) {
707 auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
708 return env->NewStringUTF(title.c_str());
709}
710
711jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription([[maybe_unused]] JNIEnv* env,
712 [[maybe_unused]] jclass clazz,
713 jstring j_filename) {
714 return j_filename;
715}
716
717jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId([[maybe_unused]] JNIEnv* env,
718 [[maybe_unused]] jclass clazz,
719 jstring j_filename) {
720 return j_filename;
721}
722
723jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions([[maybe_unused]] JNIEnv* env,
724 [[maybe_unused]] jclass clazz,
725 [[maybe_unused]] jstring j_filename) {
726 return env->NewStringUTF("");
727}
728
729jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv* env,
730 [[maybe_unused]] jclass clazz,
731 [[maybe_unused]] jstring j_filename) {
732 return env->NewStringUTF("");
733}
734
735jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
736 [[maybe_unused]] jclass clazz,
737 [[maybe_unused]] jstring j_filename) {
738 return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
739}
740
741void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
742 [[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
743 // Create the default config.ini.
744 Config{};
745 // Initialize the emulated system.
746 EmulationSession::GetInstance().System().Initialize();
747}
748
749jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore([[maybe_unused]] JNIEnv* env,
750 [[maybe_unused]] jclass clazz) {
751 return {};
752}
753
754void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
755 [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, [[maybe_unused]] jstring j_file,
756 [[maybe_unused]] jstring j_savestate, [[maybe_unused]] jboolean j_delete_savestate) {}
757
758void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env,
759 [[maybe_unused]] jclass clazz) {
760 Config{};
761}
762
763jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JNIEnv* env,
764 [[maybe_unused]] jclass clazz,
765 jstring j_game_id, jstring j_section,
766 jstring j_key) {
767 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
768 std::string_view section = env->GetStringUTFChars(j_section, 0);
769 std::string_view key = env->GetStringUTFChars(j_key, 0);
770
771 env->ReleaseStringUTFChars(j_game_id, game_id.data());
772 env->ReleaseStringUTFChars(j_section, section.data());
773 env->ReleaseStringUTFChars(j_key, key.data());
774
775 return env->NewStringUTF("");
776}
777
778void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEnv* env,
779 [[maybe_unused]] jclass clazz,
780 jstring j_game_id, jstring j_section,
781 jstring j_key, jstring j_value) {
782 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
783 std::string_view section = env->GetStringUTFChars(j_section, 0);
784 std::string_view key = env->GetStringUTFChars(j_key, 0);
785 std::string_view value = env->GetStringUTFChars(j_value, 0);
786
787 env->ReleaseStringUTFChars(j_game_id, game_id.data());
788 env->ReleaseStringUTFChars(j_section, section.data());
789 env->ReleaseStringUTFChars(j_key, key.data());
790 env->ReleaseStringUTFChars(j_value, value.data());
791}
792
793void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni([[maybe_unused]] JNIEnv* env,
794 [[maybe_unused]] jclass clazz,
795 jstring j_game_id) {
796 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
797
798 env->ReleaseStringUTFChars(j_game_id, game_id.data());
799}
800
801jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]] JNIEnv* env,
802 [[maybe_unused]] jclass clazz) {
803 jdoubleArray j_stats = env->NewDoubleArray(4);
804
805 if (EmulationSession::GetInstance().IsRunning()) {
806 const auto results = EmulationSession::GetInstance().PerfStats();
807
808 // Converting the structure into an array makes it easier to pass it to the frontend
809 double stats[4] = {results.system_fps, results.average_game_fps, results.frametime,
810 results.emulation_speed};
811
812 env->SetDoubleArrayRegion(j_stats, 0, 4, stats);
813 }
814
815 return j_stats;
816}
817
818void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(
819 [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_path) {}
820
821void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unused]] JNIEnv* env,
822 [[maybe_unused]] jclass clazz,
823 jstring j_path) {
824 const std::string path = GetJString(env, j_path);
825
826 const Core::SystemResultStatus result{RunEmulation(path)};
827 if (result != Core::SystemResultStatus::Success) {
828 env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
829 IDCache::GetExitEmulationActivity(), static_cast<int>(result));
830 }
831}
832
833void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv* env,
834 [[maybe_unused]] jclass clazz) {
835 LOG_INFO(Frontend, "yuzu Version: {}-{}", Common::g_scm_branch, Common::g_scm_desc);
836 LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
837}
838
839void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardText(JNIEnv* env, jclass clazz,
840 jstring j_text) {
841 const std::u16string input = Common::UTF8ToUTF16(GetJString(env, j_text));
842 EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardText(input);
843}
844
845void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env, jclass clazz,
846 jint j_key_code) {
847 EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
848}
849
850} // extern "C"
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
new file mode 100644
index 000000000..24dcbbcb8
--- /dev/null
+++ b/src/android/app/src/main/jni/native.h
@@ -0,0 +1,165 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <jni.h>
7
8// Function calls from the Java side
9#ifdef __cplusplus
10extern "C" {
11#endif
12
13JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation(JNIEnv* env,
14 jclass clazz);
15
16JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIEnv* env,
17 jclass clazz);
18
19JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env,
20 jclass clazz);
21
22JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env,
23 jclass clazz);
24
25JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
26 jclass clazz);
27
28JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env,
29 jclass clazz);
30
31JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env,
32 jclass clazz,
33 jstring j_device,
34 jstring j_type);
35
36JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(
37 JNIEnv* env, jclass clazz, jstring j_device);
38
39JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
40 JNIEnv* env, jclass clazz, jstring j_device);
41
42JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent(
43 JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);
44
45JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMoveEvent(
46 JNIEnv* env, jclass clazz, jstring j_device, jint axis, jfloat x, jfloat y);
47
48JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadAxisEvent(
49 JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val);
50
51JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env,
52 jclass clazz,
53 jbyteArray j_data);
54
55JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env,
56 jclass clazz);
57
58JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JNIEnv* env,
59 jclass clazz,
60 jfloat x, jfloat y,
61 jboolean pressed);
62
63JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz,
64 jfloat x, jfloat y);
65
66JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env,
67 jclass clazz,
68 jstring j_file);
69
70JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz,
71 jstring j_filename);
72
73JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription(JNIEnv* env,
74 jclass clazz,
75 jstring j_filename);
76
77JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGameId(JNIEnv* env, jclass clazz,
78 jstring j_filename);
79
80JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetRegions(JNIEnv* env,
81 jclass clazz,
82 jstring j_filename);
83
84JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetCompany(JNIEnv* env,
85 jclass clazz,
86 jstring j_filename);
87
88JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGitRevision(JNIEnv* env,
89 jclass clazz);
90
91JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetAppDirectory(JNIEnv* env,
92 jclass clazz,
93 jstring j_directory);
94
95JNIEXPORT void JNICALL
96Java_org_yuzu_yuzu_1emu_NativeLibrary_Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeGpuDriver(
97 JNIEnv* env, jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
98 jstring custom_driver_name, jstring file_redirect_dir);
99
100JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env,
101 jclass clazz);
102
103JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_SetSysDirectory(
104 JNIEnv* env, jclass clazz, jstring path_);
105
106JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetSysDirectory(JNIEnv* env,
107 jclass clazz,
108 jstring path);
109
110JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeEmulation(JNIEnv* env,
111 jclass clazz);
112
113JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_DefaultCPUCore(JNIEnv* env,
114 jclass clazz);
115JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetProfiling(JNIEnv* env, jclass clazz,
116 jboolean enable);
117
118JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_WriteProfileResults(JNIEnv* env,
119 jclass clazz);
120
121JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_NotifyOrientationChange(
122 JNIEnv* env, jclass clazz, jint layout_option, jint rotation);
123
124JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2(
125 JNIEnv* env, jclass clazz, jstring j_path);
126
127JNIEXPORT void JNICALL
128Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z(
129 JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate);
130
131JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env,
132 jclass clazz,
133 jobject surf);
134
135JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env,
136 jclass clazz);
137
138JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitGameIni(JNIEnv* env, jclass clazz,
139 jstring j_game_id);
140
141JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadSettings(JNIEnv* env,
142 jclass clazz);
143
144JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetUserSetting(
145 JNIEnv* env, jclass clazz, jstring j_game_id, jstring j_section, jstring j_key,
146 jstring j_value);
147
148JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetUserSetting(
149 JNIEnv* env, jclass clazz, jstring game_id, jstring section, jstring key);
150
151JNIEXPORT jdoubleArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetPerfStats(JNIEnv* env,
152 jclass clazz);
153
154JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_LogDeviceInfo(JNIEnv* env,
155 jclass clazz);
156
157JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardText(
158 JNIEnv* env, jclass clazz, jstring j_text);
159
160JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardInput(
161 JNIEnv* env, jclass clazz, jint j_key_code);
162
163#ifdef __cplusplus
164}
165#endif
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
new file mode 100644
index 000000000..9f49c133a
--- /dev/null
+++ b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
@@ -0,0 +1,16 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="125"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10 <translate
11 android:duration="125"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="0"
14 android:toXDelta="-75" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
new file mode 100644
index 000000000..82fd719db
--- /dev/null
+++ b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
@@ -0,0 +1,16 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="0"
8 android:toAlpha="1" />
9
10 <translate
11 android:duration="@android:integer/config_shortAnimTime"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="-200"
14 android:toXDelta="0" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
new file mode 100644
index 000000000..5892128f1
--- /dev/null
+++ b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
@@ -0,0 +1,16 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="125"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10 <translate
11 android:duration="125"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="0"
14 android:toXDelta="75" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
new file mode 100644
index 000000000..98e0cf8bd
--- /dev/null
+++ b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
@@ -0,0 +1,16 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="0"
8 android:toAlpha="1" />
9
10 <translate
11 android:duration="@android:integer/config_shortAnimTime"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="200"
14 android:toXDelta="0" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
new file mode 100644
index 000000000..77a40a4d1
--- /dev/null
+++ b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10</set>
diff --git a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
new file mode 100644
index 000000000..4612aee13
--- /dev/null
+++ b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <objectAnimator
5 android:propertyName="translationX"
6 android:valueType="floatType"
7 android:valueFrom="-1280dp"
8 android:valueTo="0"
9 android:interpolator="@android:interpolator/decelerate_quad"
10 android:duration="300"/>
11
12 <objectAnimator
13 android:propertyName="alpha"
14 android:valueType="floatType"
15 android:valueFrom="0"
16 android:valueTo="1"
17 android:interpolator="@android:interpolator/accelerate_quad"
18 android:duration="300"/>
19
20</set> \ No newline at end of file
diff --git a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
new file mode 100644
index 000000000..c00478946
--- /dev/null
+++ b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <!-- This animation is used ONLY when a submenu is replaced. -->
5 <objectAnimator
6 android:propertyName="translationX"
7 android:valueType="floatType"
8 android:valueFrom="0"
9 android:valueTo="-1280dp"
10 android:interpolator="@android:interpolator/decelerate_quad"
11 android:duration="200"/>
12
13 <objectAnimator
14 android:propertyName="alpha"
15 android:valueType="floatType"
16 android:valueFrom="1"
17 android:valueTo="0"
18 android:interpolator="@android:interpolator/decelerate_quad"
19 android:duration="200"/>
20
21</set> \ No newline at end of file
diff --git a/src/android/app/src/main/res/drawable-hdpi/ic_stat_notification_logo.png b/src/android/app/src/main/res/drawable-hdpi/ic_stat_notification_logo.png
new file mode 100644
index 000000000..66ebfa85c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable-hdpi/ic_stat_notification_logo.png
Binary files differ
diff --git a/src/android/app/src/main/res/drawable-xhdpi/ic_stat_notification_logo.png b/src/android/app/src/main/res/drawable-xhdpi/ic_stat_notification_logo.png
new file mode 100644
index 000000000..71068f452
--- /dev/null
+++ b/src/android/app/src/main/res/drawable-xhdpi/ic_stat_notification_logo.png
Binary files differ
diff --git a/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png b/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png
new file mode 100644
index 000000000..20c770591
--- /dev/null
+++ b/src/android/app/src/main/res/drawable-xhdpi/tv_banner.png
Binary files differ
diff --git a/src/android/app/src/main/res/drawable-xxhdpi/ic_stat_notification_logo.png b/src/android/app/src/main/res/drawable-xxhdpi/ic_stat_notification_logo.png
new file mode 100644
index 000000000..d73fad15b
--- /dev/null
+++ b/src/android/app/src/main/res/drawable-xxhdpi/ic_stat_notification_logo.png
Binary files differ
diff --git a/src/android/app/src/main/res/drawable/default_icon.jpg b/src/android/app/src/main/res/drawable/default_icon.jpg
new file mode 100644
index 000000000..859caf4af
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/default_icon.jpg
Binary files differ
diff --git a/src/android/app/src/main/res/drawable/dpad_standard.xml b/src/android/app/src/main/res/drawable/dpad_standard.xml
new file mode 100644
index 000000000..28aba657e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/dpad_standard.xml
@@ -0,0 +1,24 @@
1<vector android:alpha="0.6" android:height="221.78dp"
2 android:viewportHeight="221.78" android:viewportWidth="221.78"
3 android:width="221.78dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.75"
6 android:pathData="M221.78,87.07v47.64a11.53,11.53 0,0 1,-11.5 11.5H151.62a5.42,5.42 0,0 0,-5.41 5.41v58.66a11.53,11.53 0,0 1,-11.5 11.5H87.07a11.53,11.53 0,0 1,-11.5 -11.5V151.61a5.41,5.41 0,0 0,-5.41 -5.41H11.5A11.53,11.53 0,0 1,0 134.7V87.05a11.53,11.53 0,0 1,11.5 -11.5H70.16a5.41,5.41 0,0 0,5.41 -5.41V11.5A11.53,11.53 0,0 1,87.07 0h47.64a11.53,11.53 0,0 1,11.5 11.5V70.16a5.41,5.41 0,0 0,5.41 5.41h58.66A11.53,11.53 0,0 1,221.78 87.07Z" android:strokeAlpha="0.75">
7 <aapt:attr name="android:fillColor">
8 <gradient android:centerX="110.89" android:centerY="110.89"
9 android:gradientRadius="110.89" android:type="radial">
10 <item android:color="#FFC3C4C5" android:offset="0.58"/>
11 <item android:color="#FFC6C6C6" android:offset="0.84"/>
12 <item android:color="#FFC7C7C7" android:offset="0.88"/>
13 <item android:color="#FFC2C2C2" android:offset="0.91"/>
14 <item android:color="#FFB5B5B5" android:offset="0.94"/>
15 <item android:color="#FF9E9E9E" android:offset="0.98"/>
16 <item android:color="#FF8F8F8F" android:offset="1"/>
17 </gradient>
18 </aapt:attr>
19 </path>
20 <path android:fillColor="#FF000000" android:pathData="M195.47,110.32l-16.26,-9.38a0.65,0.65 0,0 0,-1 0.56v18.78a0.66,0.66 0,0 0,1 0.57l16.26,-9.39A0.66,0.66 0,0 0,195.47 110.32Z"/>
21 <path android:fillColor="#FF000000" android:pathData="M26.31,110.32l16.26,-9.38a0.65,0.65 0,0 1,1 0.56v18.78a0.66,0.66 0,0 1,-1 0.57l-16.26,-9.39A0.66,0.66 0,0 1,26.31 110.32Z"/>
22 <path android:fillColor="#FF000000" android:pathData="M110.32,26.31l-9.38,16.26a0.65,0.65 0,0 0,0.56 1h18.78a0.66,0.66 0,0 0,0.57 -1l-9.39,-16.26A0.66,0.66 0,0 0,110.32 26.31Z"/>
23 <path android:fillColor="#FF000000" android:pathData="M110.32,195.47l-9.38,-16.26a0.65,0.65 0,0 1,0.56 -1h18.78a0.66,0.66 0,0 1,0.57 1l-9.39,16.26A0.66,0.66 0,0 1,110.32 195.47Z"/>
24</vector>
diff --git a/src/android/app/src/main/res/drawable/dpad_standard_cardinal_depressed.xml b/src/android/app/src/main/res/drawable/dpad_standard_cardinal_depressed.xml
new file mode 100644
index 000000000..5eeb51dbe
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/dpad_standard_cardinal_depressed.xml
@@ -0,0 +1,24 @@
1<vector android:alpha="0.6" android:height="221.78dp"
2 android:viewportHeight="221.78" android:viewportWidth="221.78"
3 android:width="221.78dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.5"
6 android:pathData="M221.78,87.07v47.64a11.53,11.53 0,0 1,-11.5 11.5H151.62a5.42,5.42 0,0 0,-5.41 5.41v58.66a11.53,11.53 0,0 1,-11.5 11.5H87.07a11.53,11.53 0,0 1,-11.5 -11.5V151.61a5.41,5.41 0,0 0,-5.41 -5.41H11.5A11.53,11.53 0,0 1,0 134.7V87.05a11.53,11.53 0,0 1,11.5 -11.5H70.16a5.41,5.41 0,0 0,5.41 -5.41V11.5A11.53,11.53 0,0 1,87.07 0h47.64a11.53,11.53 0,0 1,11.5 11.5V70.16a5.41,5.41 0,0 0,5.41 5.41h58.66A11.53,11.53 0,0 1,221.78 87.07Z" android:strokeAlpha="0.5">
7 <aapt:attr name="android:fillColor">
8 <gradient android:endX="110.89" android:endY="-38.27"
9 android:startX="110.89" android:startY="183.51" android:type="linear">
10 <item android:color="#7F000000" android:offset="0"/>
11 <item android:color="#BA000000" android:offset="0.43"/>
12 <item android:color="#FF000000" android:offset="0.5"/>
13 </gradient>
14 </aapt:attr>
15 </path>
16 <path android:fillAlpha="0.1" android:fillColor="#fff"
17 android:pathData="M195.47,110.32l-16.26,-9.38a0.65,0.65 0,0 0,-1 0.56v18.78a0.66,0.66 0,0 0,1 0.57l16.26,-9.39A0.66,0.66 0,0 0,195.47 110.32Z" android:strokeAlpha="0.1"/>
18 <path android:fillAlpha="0.1" android:fillColor="#fff"
19 android:pathData="M26.31,110.32l16.26,-9.38a0.65,0.65 0,0 1,1 0.56v18.78a0.66,0.66 0,0 1,-1 0.57l-16.26,-9.39A0.66,0.66 0,0 1,26.31 110.32Z" android:strokeAlpha="0.1"/>
20 <path android:fillAlpha="0.75" android:fillColor="#fff"
21 android:pathData="M110.32,26.31l-9.38,16.26a0.65,0.65 0,0 0,0.56 1h18.78a0.66,0.66 0,0 0,0.57 -1l-9.39,-16.26A0.66,0.66 0,0 0,110.32 26.31Z" android:strokeAlpha="0.75"/>
22 <path android:fillAlpha="0.1" android:fillColor="#fff"
23 android:pathData="M110.32,195.47l-9.38,-16.26a0.65,0.65 0,0 1,0.56 -1h18.78a0.66,0.66 0,0 1,0.57 1l-9.39,16.26A0.66,0.66 0,0 1,110.32 195.47Z" android:strokeAlpha="0.1"/>
24</vector>
diff --git a/src/android/app/src/main/res/drawable/dpad_standard_diagonal_depressed.xml b/src/android/app/src/main/res/drawable/dpad_standard_diagonal_depressed.xml
new file mode 100644
index 000000000..520fd447c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/dpad_standard_diagonal_depressed.xml
@@ -0,0 +1,24 @@
1<vector android:alpha="0.6" android:height="221.78dp"
2 android:viewportHeight="221.78" android:viewportWidth="221.78"
3 android:width="221.78dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.5"
6 android:pathData="M221.78,87.07v47.64a11.53,11.53 0,0 1,-11.5 11.5H151.62a5.42,5.42 0,0 0,-5.41 5.41v58.66a11.53,11.53 0,0 1,-11.5 11.5H87.07a11.53,11.53 0,0 1,-11.5 -11.5V151.61a5.41,5.41 0,0 0,-5.41 -5.41H11.5A11.53,11.53 0,0 1,0 134.7V87.05a11.53,11.53 0,0 1,11.5 -11.5H70.16a5.41,5.41 0,0 0,5.41 -5.41V11.5A11.53,11.53 0,0 1,87.07 0h47.64a11.53,11.53 0,0 1,11.5 11.5V70.16a5.41,5.41 0,0 0,5.41 5.41h58.66A11.53,11.53 0,0 1,221.78 87.07Z" android:strokeAlpha="0.5">
7 <aapt:attr name="android:fillColor">
8 <gradient android:endX="31.24" android:endY="31.24"
9 android:startX="188.07" android:startY="188.07" android:type="linear">
10 <item android:color="#7F000000" android:offset="0"/>
11 <item android:color="#BA000000" android:offset="0.43"/>
12 <item android:color="#FF000000" android:offset="0.5"/>
13 </gradient>
14 </aapt:attr>
15 </path>
16 <path android:fillAlpha="0.1" android:fillColor="#fff"
17 android:pathData="M195.47,110.32l-16.26,-9.38a0.65,0.65 0,0 0,-1 0.56v18.78a0.66,0.66 0,0 0,1 0.57l16.26,-9.39A0.66,0.66 0,0 0,195.47 110.32Z" android:strokeAlpha="0.1"/>
18 <path android:fillAlpha="0.75" android:fillColor="#fff"
19 android:pathData="M26.31,110.32l16.26,-9.38a0.65,0.65 0,0 1,1 0.56v18.78a0.66,0.66 0,0 1,-1 0.57l-16.26,-9.39A0.66,0.66 0,0 1,26.31 110.32Z" android:strokeAlpha="0.75"/>
20 <path android:fillAlpha="0.75" android:fillColor="#fff"
21 android:pathData="M110.32,26.31l-9.38,16.26a0.65,0.65 0,0 0,0.56 1h18.78a0.66,0.66 0,0 0,0.57 -1l-9.39,-16.26A0.66,0.66 0,0 0,110.32 26.31Z" android:strokeAlpha="0.75"/>
22 <path android:fillAlpha="0.1" android:fillColor="#fff"
23 android:pathData="M110.32,195.47l-9.38,-16.26a0.65,0.65 0,0 1,0.56 -1h18.78a0.66,0.66 0,0 1,0.57 1l-9.39,16.26A0.66,0.66 0,0 1,110.32 195.47Z" android:strokeAlpha="0.1"/>
24</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_a.xml b/src/android/app/src/main/res/drawable/facebutton_a.xml
new file mode 100644
index 000000000..668652edb
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_a.xml
@@ -0,0 +1,22 @@
1<vector android:alpha="0.6" android:height="103.61dp"
2 android:viewportHeight="103.61" android:viewportWidth="103.61"
3 android:width="103.61dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.6"
6 android:pathData="M51.8,51.8m-51.8,0a51.8,51.8 0,1 1,103.6 0a51.8,51.8 0,1 1,-103.6 0" android:strokeAlpha="0.6">
7 <aapt:attr name="android:fillColor">
8 <gradient android:centerX="51.8" android:centerY="51.8"
9 android:gradientRadius="51.8" android:type="radial">
10 <item android:color="#FFC3C4C5" android:offset="0.58"/>
11 <item android:color="#FFC6C6C6" android:offset="0.84"/>
12 <item android:color="#FFC7C7C7" android:offset="0.88"/>
13 <item android:color="#FFC2C2C2" android:offset="0.91"/>
14 <item android:color="#FFB5B5B5" android:offset="0.94"/>
15 <item android:color="#FF9E9E9E" android:offset="0.98"/>
16 <item android:color="#FF8F8F8F" android:offset="1"/>
17 </gradient>
18 </aapt:attr>
19 </path>
20 <path android:fillAlpha="0.6" android:fillColor="#FF000000"
21 android:pathData="M49.88,34.36h4.29L69.1,69.25L63.58,69.25l-3.5,-8.63L43.48,60.62L40,69.25L34.51,69.25ZM58.36,56.48 L51.85,40.48h-0.1l-6.6,16Z" android:strokeAlpha="0.6"/>
22</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_a_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_a_depressed.xml
new file mode 100644
index 000000000..4fbe06962
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_a_depressed.xml
@@ -0,0 +1,8 @@
1<vector android:alpha="0.6" android:height="103.61dp"
2 android:viewportHeight="103.61" android:viewportWidth="103.61"
3 android:width="103.61dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M51.8,51.8m-51.8,0a51.8,51.8 0,1 1,103.6 0a51.8,51.8 0,1 1,-103.6 0" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M49.88,34.36h4.29L69.1,69.25L63.58,69.25l-3.5,-8.63L43.48,60.62L40,69.25L34.51,69.25ZM58.36,56.48 L51.85,40.48h-0.1l-6.6,16Z" android:strokeAlpha="0.75"/>
8</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_b.xml b/src/android/app/src/main/res/drawable/facebutton_b.xml
new file mode 100644
index 000000000..8912219ca
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_b.xml
@@ -0,0 +1,22 @@
1<vector android:alpha="0.6" android:height="103.61dp"
2 android:viewportHeight="103.61" android:viewportWidth="103.61"
3 android:width="103.61dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.6"
6 android:pathData="M51.8,51.8m-51.8,0a51.8,51.8 0,1 1,103.6 0a51.8,51.8 0,1 1,-103.6 0" android:strokeAlpha="0.6">
7 <aapt:attr name="android:fillColor">
8 <gradient android:centerX="51.8" android:centerY="51.8"
9 android:gradientRadius="51.8" android:type="radial">
10 <item android:color="#FFC3C4C5" android:offset="0.58"/>
11 <item android:color="#FFC6C6C6" android:offset="0.84"/>
12 <item android:color="#FFC7C7C7" android:offset="0.88"/>
13 <item android:color="#FFC2C2C2" android:offset="0.91"/>
14 <item android:color="#FFB5B5B5" android:offset="0.94"/>
15 <item android:color="#FF9E9E9E" android:offset="0.98"/>
16 <item android:color="#FF8F8F8F" android:offset="1"/>
17 </gradient>
18 </aapt:attr>
19 </path>
20 <path android:fillAlpha="0.6" android:fillColor="#FF000000"
21 android:pathData="M41,35.67L53.15,35.67a15.78,15.78 0,0 1,4.22 0.54,10.07 10.07,0 0,1 3.36,1.6A7.49,7.49 0,0 1,63 40.53a8.73,8.73 0,0 1,0.81 3.88,7.13 7.13,0 0,1 -1.67,4.91 9.75,9.75 0,0 1,-4.35 2.79v0.1a7.4,7.4 0,0 1,3 0.82,8.1 8.1,0 0,1 2.4,1.87 9.14,9.14 0,0 1,2.2 6,8.73 8.73,0 0,1 -1,4.17 8.86,8.86 0,0 1,-2.64 3A12.39,12.39 0,0 1,57.79 70a17.12,17.12 0,0 1,-4.79 0.64L41,70.64ZM45.74,50.19h6.47a10.75,10.75 0,0 0,2.52 -0.28A5.56,5.56 0,0 0,56.8 49a4.73,4.73 0,0 0,1.41 -1.63A5.22,5.22 0,0 0,58.73 45a5,5 0,0 0,-5.53 -5.13L45.74,39.87ZM45.74,66.48h7a14.17,14.17 0,0 0,2.4 -0.22,7.23 7.23,0 0,0 2.44,-0.89 6,6 0,0 0,1.93 -1.8,5.15 5.15,0 0,0 0.79,-3 5.52,5.52 0,0 0,-2 -4.67,8.75 8.75,0 0,0 -5.48,-1.56h-7Z" android:strokeAlpha="0.6"/>
22</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_b_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_b_depressed.xml
new file mode 100644
index 000000000..012abeaf1
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_b_depressed.xml
@@ -0,0 +1,8 @@
1<vector android:alpha="0.6" android:height="103.61dp"
2 android:viewportHeight="103.61" android:viewportWidth="103.61"
3 android:width="103.61dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M51.8,51.8m-51.8,0a51.8,51.8 0,1 1,103.6 0a51.8,51.8 0,1 1,-103.6 0" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M41,35.67L53.15,35.67a15.78,15.78 0,0 1,4.22 0.54,10.07 10.07,0 0,1 3.36,1.6A7.49,7.49 0,0 1,63 40.53a8.73,8.73 0,0 1,0.81 3.88,7.13 7.13,0 0,1 -1.67,4.91 9.75,9.75 0,0 1,-4.35 2.79v0.1a7.4,7.4 0,0 1,3 0.82,8.1 8.1,0 0,1 2.4,1.87 9.14,9.14 0,0 1,2.2 6,8.73 8.73,0 0,1 -1,4.17 8.86,8.86 0,0 1,-2.64 3A12.39,12.39 0,0 1,57.79 70a17.12,17.12 0,0 1,-4.79 0.64L41,70.64ZM45.74,50.19h6.47a10.75,10.75 0,0 0,2.52 -0.28A5.56,5.56 0,0 0,56.8 49a4.73,4.73 0,0 0,1.41 -1.63A5.22,5.22 0,0 0,58.73 45a5,5 0,0 0,-5.53 -5.13L45.74,39.87ZM45.74,66.48h7a14.17,14.17 0,0 0,2.4 -0.22,7.23 7.23,0 0,0 2.44,-0.89 6,6 0,0 0,1.93 -1.8,5.15 5.15,0 0,0 0.79,-3 5.52,5.52 0,0 0,-2 -4.67,8.75 8.75,0 0,0 -5.48,-1.56h-7Z" android:strokeAlpha="0.75"/>
8</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_home.xml b/src/android/app/src/main/res/drawable/facebutton_home.xml
new file mode 100644
index 000000000..03596ec2e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_home.xml
@@ -0,0 +1,21 @@
1<vector android:alpha="0.6" android:height="70.55dp"
2 android:viewportHeight="70.55" android:viewportWidth="70.55"
3 android:width="70.55dp" xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.75"
5 android:pathData="M35.27,35.27m-35.27,0a35.27,35.27 0,1 1,70.54 0a35.27,35.27 0,1 1,-70.54 0" android:strokeAlpha="0.75">
6 <aapt:attr name="android:fillColor">
7 <gradient android:centerX="35.27" android:centerY="35.27"
8 android:gradientRadius="35.27" android:type="radial">
9 <item android:color="#FFC3C4C5" android:offset="0.58"/>
10 <item android:color="#FFC6C6C6" android:offset="0.84"/>
11 <item android:color="#FFC7C7C7" android:offset="0.88"/>
12 <item android:color="#FFC2C2C2" android:offset="0.91"/>
13 <item android:color="#FFB5B5B5" android:offset="0.94"/>
14 <item android:color="#FF9E9E9E" android:offset="0.98"/>
15 <item android:color="#FF8F8F8F" android:offset="1"/>
16 </gradient>
17 </aapt:attr>
18 </path>
19 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
20 android:pathData="M55.19,32.72 L36.06,15.21a1.14,1.14 0,0 0,-1.57 0L15.36,32.72a1.13,1.13 0,0 0,0.79 1.94H19.4a0.72,0.72 0,0 1,0.72 0.72V51.49a1.13,1.13 0,0 0,1.12 1.13H49.31a1.13,1.13 0,0 0,1.12 -1.13V35.38a0.72,0.72 0,0 1,0.72 -0.72H54.4A1.13,1.13 0,0 0,55.19 32.72ZM41.45,43.86a0.9,0.9 0,0 1,-0.9 0.9H30a0.9,0.9 0,0 1,-0.9 -0.9V35.55a0.89,0.89 0,0 1,0.9 -0.89H40.55a0.89,0.89 0,0 1,0.9 0.89Z" android:strokeAlpha="0.75"/>
21</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_home_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_home_depressed.xml
new file mode 100644
index 000000000..cde7c6a9e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_home_depressed.xml
@@ -0,0 +1,8 @@
1<vector android:alpha="0.6" android:height="70.55dp"
2 android:viewportHeight="70.55" android:viewportWidth="70.55"
3 android:width="70.55dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M35.27,35.27m-35.27,0a35.27,35.27 0,1 1,70.54 0a35.27,35.27 0,1 1,-70.54 0" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M55.19,32.72 L36.06,15.21a1.14,1.14 0,0 0,-1.57 0L15.36,32.72a1.13,1.13 0,0 0,0.79 1.94H19.4a0.72,0.72 0,0 1,0.72 0.72V51.49a1.13,1.13 0,0 0,1.12 1.13H49.31a1.13,1.13 0,0 0,1.12 -1.13V35.38a0.72,0.72 0,0 1,0.72 -0.72H54.4A1.13,1.13 0,0 0,55.19 32.72ZM41.45,43.86a0.9,0.9 0,0 1,-0.9 0.9H30a0.9,0.9 0,0 1,-0.9 -0.9V35.55a0.89,0.89 0,0 1,0.9 -0.89H40.55a0.89,0.89 0,0 1,0.9 0.89Z" android:strokeAlpha="0.75"/>
8</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_minus.xml b/src/android/app/src/main/res/drawable/facebutton_minus.xml
new file mode 100644
index 000000000..4296b4fcc
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_minus.xml
@@ -0,0 +1,22 @@
1<vector android:alpha="0.6" android:height="69.95dp"
2 android:viewportHeight="69.95" android:viewportWidth="69.95"
3 android:width="69.95dp" xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.75"
5 android:pathData="M34.97,34.97m-34.97,0a34.97,34.97 0,1 1,69.94 0a34.97,34.97 0,1 1,-69.94 0" android:strokeAlpha="0.75">
6 <aapt:attr name="android:fillColor">
7 <gradient android:centerX="34.97" android:centerY="34.97"
8 android:gradientRadius="34.97" android:type="radial">
9 <item android:color="#FFC3C4C5" android:offset="0.58"/>
10 <item android:color="#FFC6C6C6" android:offset="0.84"/>
11 <item android:color="#FFC7C7C7" android:offset="0.88"/>
12 <item android:color="#FFC2C2C2" android:offset="0.91"/>
13 <item android:color="#FFB5B5B5" android:offset="0.94"/>
14 <item android:color="#FF9E9E9E" android:offset="0.98"/>
15 <item android:color="#FF8F8F8F" android:offset="1"/>
16 </gradient>
17 </aapt:attr>
18 </path>
19 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
20 android:pathData="M52,38.28H17.91a0.52,0.52 0,0 1,-0.52 -0.52V32.19a0.52,0.52 0,0 1,0.52 -0.52H52a0.52,0.52 0,0 1,0.52 0.52v5.57A0.52,0.52 0,0 1,52 38.28Z"
21 android:strokeAlpha="0.75" android:strokeColor="#000" android:strokeWidth="2.5"/>
22</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_minus_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_minus_depressed.xml
new file mode 100644
index 000000000..628027841
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_minus_depressed.xml
@@ -0,0 +1,9 @@
1<vector android:alpha="0.6" android:height="69.95dp"
2 android:viewportHeight="69.95" android:viewportWidth="69.95"
3 android:width="69.95dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M34.97,34.97m-34.97,0a34.97,34.97 0,1 1,69.94 0a34.97,34.97 0,1 1,-69.94 0" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M52,38.28H17.91a0.52,0.52 0,0 1,-0.52 -0.52V32.19a0.52,0.52 0,0 1,0.52 -0.52H52a0.52,0.52 0,0 1,0.52 0.52v5.57A0.52,0.52 0,0 1,52 38.28Z"
8 android:strokeAlpha="0.75" android:strokeColor="#fff" android:strokeWidth="2.5"/>
9</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_plus.xml b/src/android/app/src/main/res/drawable/facebutton_plus.xml
new file mode 100644
index 000000000..43ae14365
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_plus.xml
@@ -0,0 +1,22 @@
1<vector android:alpha="0.6" android:height="69.95dp"
2 android:viewportHeight="69.95" android:viewportWidth="69.95"
3 android:width="69.95dp" xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.75"
5 android:pathData="M34.97,34.97m-34.97,0a34.97,34.97 0,1 1,69.94 0a34.97,34.97 0,1 1,-69.94 0" android:strokeAlpha="0.75">
6 <aapt:attr name="android:fillColor">
7 <gradient android:centerX="34.97" android:centerY="34.97"
8 android:gradientRadius="34.97" android:type="radial">
9 <item android:color="#FFC3C4C5" android:offset="0.58"/>
10 <item android:color="#FFC6C6C6" android:offset="0.84"/>
11 <item android:color="#FFC7C7C7" android:offset="0.88"/>
12 <item android:color="#FFC2C2C2" android:offset="0.91"/>
13 <item android:color="#FFB5B5B5" android:offset="0.94"/>
14 <item android:color="#FF9E9E9E" android:offset="0.98"/>
15 <item android:color="#FF8F8F8F" android:offset="1"/>
16 </gradient>
17 </aapt:attr>
18 </path>
19 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
20 android:pathData="M13.94,31.9H31.16a0.65,0.65 0,0 0,0.65 -0.64V14.59a0.65,0.65 0,0 1,0.64 -0.65h5a0.65,0.65 0,0 1,0.65 0.65V31.26a0.65,0.65 0,0 0,0.65 0.64H56a0.65,0.65 0,0 1,0.65 0.65V37.4a0.65,0.65 0,0 1,-0.65 0.65H38.79a0.65,0.65 0,0 0,-0.65 0.64V55.36a0.65,0.65 0,0 1,-0.65 0.64h-5a0.64,0.64 0,0 1,-0.64 -0.64V38.69a0.65,0.65 0,0 0,-0.65 -0.64H13.94a0.65,0.65 0,0 1,-0.65 -0.65V32.55A0.65,0.65 0,0 1,13.94 31.9Z"
21 android:strokeAlpha="0.75" android:strokeColor="#000" android:strokeWidth="2.5"/>
22</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_plus_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_plus_depressed.xml
new file mode 100644
index 000000000..c510e136e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_plus_depressed.xml
@@ -0,0 +1,9 @@
1<vector android:alpha="0.6" android:height="69.95dp"
2 android:viewportHeight="69.95" android:viewportWidth="69.95"
3 android:width="69.95dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M34.97,34.97m-34.97,0a34.97,34.97 0,1 1,69.94 0a34.97,34.97 0,1 1,-69.94 0" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M13.94,31.9H31.16a0.65,0.65 0,0 0,0.65 -0.64V14.59a0.65,0.65 0,0 1,0.64 -0.65h5a0.65,0.65 0,0 1,0.65 0.65V31.26a0.65,0.65 0,0 0,0.65 0.64H56a0.65,0.65 0,0 1,0.65 0.65V37.4a0.65,0.65 0,0 1,-0.65 0.65H38.79a0.65,0.65 0,0 0,-0.65 0.64V55.36a0.65,0.65 0,0 1,-0.65 0.64h-5a0.64,0.64 0,0 1,-0.64 -0.64V38.69a0.65,0.65 0,0 0,-0.65 -0.64H13.94a0.65,0.65 0,0 1,-0.65 -0.65V32.55A0.65,0.65 0,0 1,13.94 31.9Z"
8 android:strokeAlpha="0.75" android:strokeColor="#fff" android:strokeWidth="2.5"/>
9</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_screenshot.xml b/src/android/app/src/main/res/drawable/facebutton_screenshot.xml
new file mode 100644
index 000000000..984b4fd02
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_screenshot.xml
@@ -0,0 +1,21 @@
1<vector android:alpha="0.6" android:height="70dp"
2 android:viewportHeight="70" android:viewportWidth="70"
3 android:width="70dp" xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.75"
5 android:pathData="M10.63,0L59.37,0A10.63,10.63 0,0 1,70 10.63L70,59.37A10.63,10.63 0,0 1,59.37 70L10.63,70A10.63,10.63 0,0 1,0 59.37L0,10.63A10.63,10.63 0,0 1,10.63 0z" android:strokeAlpha="0.75">
6 <aapt:attr name="android:fillColor">
7 <gradient android:centerX="35" android:centerY="35"
8 android:gradientRadius="42.51" android:type="radial">
9 <item android:color="#FFC3C4C5" android:offset="0.58"/>
10 <item android:color="#FFC6C6C6" android:offset="0.84"/>
11 <item android:color="#FFC7C7C7" android:offset="0.88"/>
12 <item android:color="#FFC2C2C2" android:offset="0.91"/>
13 <item android:color="#FFB5B5B5" android:offset="0.94"/>
14 <item android:color="#FF9E9E9E" android:offset="0.98"/>
15 <item android:color="#FF8F8F8F" android:offset="1"/>
16 </gradient>
17 </aapt:attr>
18 </path>
19 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
20 android:pathData="M35,35m-21.5,0a21.5,21.5 0,1 1,43 0a21.5,21.5 0,1 1,-43 0" android:strokeAlpha="0.75"/>
21</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_screenshot_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_screenshot_depressed.xml
new file mode 100644
index 000000000..fd2e44294
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_screenshot_depressed.xml
@@ -0,0 +1,8 @@
1<vector android:alpha="0.6" android:height="70dp"
2 android:viewportHeight="70" android:viewportWidth="70"
3 android:width="70dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M10.63,0L59.37,0A10.63,10.63 0,0 1,70 10.63L70,59.37A10.63,10.63 0,0 1,59.37 70L10.63,70A10.63,10.63 0,0 1,0 59.37L0,10.63A10.63,10.63 0,0 1,10.63 0z" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M35,35m-21.5,0a21.5,21.5 0,1 1,43 0a21.5,21.5 0,1 1,-43 0" android:strokeAlpha="0.75"/>
8</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_x.xml b/src/android/app/src/main/res/drawable/facebutton_x.xml
new file mode 100644
index 000000000..43fdd14c4
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_x.xml
@@ -0,0 +1,22 @@
1<vector android:alpha="0.6" android:height="103.61dp"
2 android:viewportHeight="103.61" android:viewportWidth="103.61"
3 android:width="103.61dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.6"
6 android:pathData="M51.8,51.8m-51.8,0a51.8,51.8 0,1 1,103.6 0a51.8,51.8 0,1 1,-103.6 0" android:strokeAlpha="0.6">
7 <aapt:attr name="android:fillColor">
8 <gradient android:centerX="51.8" android:centerY="51.8"
9 android:gradientRadius="51.8" android:type="radial">
10 <item android:color="#FFC3C4C5" android:offset="0.58"/>
11 <item android:color="#FFC6C6C6" android:offset="0.84"/>
12 <item android:color="#FFC7C7C7" android:offset="0.88"/>
13 <item android:color="#FFC2C2C2" android:offset="0.91"/>
14 <item android:color="#FFB5B5B5" android:offset="0.94"/>
15 <item android:color="#FF9E9E9E" android:offset="0.98"/>
16 <item android:color="#FF8F8F8F" android:offset="1"/>
17 </gradient>
18 </aapt:attr>
19 </path>
20 <path android:fillAlpha="0.6" android:fillColor="#FF000000"
21 android:pathData="M48.39,50.91 L36.63,34.31h6.08L51.8,47.75l9,-13.44h5.93L55.07,50.86 67.92,69.3H61.69l-10,-15.17L41.57,69.3H35.69Z" android:strokeAlpha="0.6"/>
22</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_x_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_x_depressed.xml
new file mode 100644
index 000000000..a9ba49169
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_x_depressed.xml
@@ -0,0 +1,8 @@
1<vector android:alpha="0.6" android:height="103.61dp"
2 android:viewportHeight="103.61" android:viewportWidth="103.61"
3 android:width="103.61dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M51.8,51.8m-51.8,0a51.8,51.8 0,1 1,103.6 0a51.8,51.8 0,1 1,-103.6 0" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M48.39,50.91 L36.63,34.31h6.08L51.8,47.75l9,-13.44h5.93L55.07,50.86 67.92,69.3H61.69l-10,-15.17L41.57,69.3H35.69Z" android:strokeAlpha="0.75"/>
8</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_y.xml b/src/android/app/src/main/res/drawable/facebutton_y.xml
new file mode 100644
index 000000000..980be3b2e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_y.xml
@@ -0,0 +1,22 @@
1<vector android:alpha="0.6" android:height="103.61dp"
2 android:viewportHeight="103.61" android:viewportWidth="103.61"
3 android:width="103.61dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.6"
6 android:pathData="M51.8,51.8m-51.8,0a51.8,51.8 0,1 1,103.6 0a51.8,51.8 0,1 1,-103.6 0" android:strokeAlpha="0.6">
7 <aapt:attr name="android:fillColor">
8 <gradient android:centerX="51.8" android:centerY="51.8"
9 android:gradientRadius="51.8" android:type="radial">
10 <item android:color="#FFC3C4C5" android:offset="0.58"/>
11 <item android:color="#FFC6C6C6" android:offset="0.84"/>
12 <item android:color="#FFC7C7C7" android:offset="0.88"/>
13 <item android:color="#FFC2C2C2" android:offset="0.91"/>
14 <item android:color="#FFB5B5B5" android:offset="0.94"/>
15 <item android:color="#FF9E9E9E" android:offset="0.98"/>
16 <item android:color="#FF8F8F8F" android:offset="1"/>
17 </gradient>
18 </aapt:attr>
19 </path>
20 <path android:fillAlpha="0.6" android:fillColor="#FF000000"
21 android:pathData="M49.43,54.37l-13.23,-20h6.07L51.8,49.68l9.83,-15.36h5.78l-13.24,20V69.29H49.43Z" android:strokeAlpha="0.6"/>
22</vector>
diff --git a/src/android/app/src/main/res/drawable/facebutton_y_depressed.xml b/src/android/app/src/main/res/drawable/facebutton_y_depressed.xml
new file mode 100644
index 000000000..320d63897
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/facebutton_y_depressed.xml
@@ -0,0 +1,8 @@
1<vector android:alpha="0.6" android:height="103.61dp"
2 android:viewportHeight="103.61" android:viewportWidth="103.61"
3 android:width="103.61dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M51.8,51.8m-51.8,0a51.8,51.8 0,1 1,103.6 0a51.8,51.8 0,1 1,-103.6 0" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M49.43,54.37l-13.23,-20h6.07L51.8,49.68l9.83,-15.36h5.78l-13.24,20V69.29H49.43Z" android:strokeAlpha="0.75"/>
8</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_add.xml b/src/android/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 000000000..f7deb2532
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_arrow_forward.xml b/src/android/app/src/main/res/drawable/ic_arrow_forward.xml
new file mode 100644
index 000000000..3b85a3e2c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_arrow_forward.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:autoMirrored="true"
5 android:viewportWidth="24"
6 android:viewportHeight="24">
7 <path
8 android:fillColor="?attr/colorControlNormal"
9 android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z" />
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_back.xml b/src/android/app/src/main/res/drawable/ic_back.xml
new file mode 100644
index 000000000..f99fea719
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_back.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:autoMirrored="true"
5 android:viewportWidth="24"
6 android:viewportHeight="24">
7 <path
8 android:fillColor="?attr/colorControlNormal"
9 android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_cartridge.xml b/src/android/app/src/main/res/drawable/ic_cartridge.xml
new file mode 100644
index 000000000..b332d9c0a
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_cartridge.xml
@@ -0,0 +1,12 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M12,7c-3.44,0 -4.16,0.35 -4.31,0.5s0,0 0,0.17v8.91a0.38,0.38 0,0 0,0.37 0.37h7.85a0.38,0.38 0,0 0,0.38 -0.37V7.53S15.41,7 12,7Z" />
9 <path
10 android:fillColor="?attr/colorControlNormal"
11 android:pathData="M22,6.51a23.12,23.12 0,0 0,-9.75 -2.1A26.09,26.09 0,0 0,2.05 6.5a1.43,1.43 0,0 0,-0.84 1.3L1.21,18.41A1.19,1.19 0,0 0,2.4 19.6L21.57,19.6a1.19,1.19 0,0 0,1.19 -1.19L22.76,7.81A1.43,1.43 0,0 0,22 6.51ZM5.56,18.59h-1v-12l1,-0.3ZM17.29,16.59A1.38,1.38 0,0 1,15.91 18L8,18a1.37,1.37 0,0 1,-1.37 -1.37L6.63,7.73A1.13,1.13 0,0 1,7 6.84c0.41,-0.41 1.3,-0.8 5,-0.8s4.57,0.38 5,0.79a1.12,1.12 0,0 1,0.31 0.87ZM18.39,18.59L18.39,6.26c0.33,0.09 0.66,0.19 1,0.31v12Z" />
12</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_cartridge_outline.xml b/src/android/app/src/main/res/drawable/ic_cartridge_outline.xml
new file mode 100644
index 000000000..cc35d7eff
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_cartridge_outline.xml
@@ -0,0 +1,12 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M22,6.5c-3.1,-1.4 -6.4,-2.1 -9.7,-2.1C8.7,4.4 5.3,5.1 2,6.5C1.5,6.7 1.2,7.2 1.2,7.8v10.6c0,0.7 0.5,1.2 1.2,1.2h19.2c0.7,0 1.2,-0.5 1.2,-1.2c0,0 0,0 0,0l0,0V7.8C22.8,7.3 22.5,6.7 22,6.5zM4.6,18.6H3.6v-3.2c0,-0.2 -0.1,-0.3 -0.2,-0.4c-0.4,-0.2 -0.8,-0.4 -1.2,-0.5V7.8c0,-0.2 0.1,-0.3 0.2,-0.4c0.7,-0.3 1.4,-0.6 2.1,-0.8V18.6zM18.4,18.6H5.6V6.3c2.2,-0.6 4.4,-0.9 6.7,-0.9c2.1,0 4.1,0.3 6.1,0.8L18.4,18.6zM21.8,14.5c-0.4,0.1 -0.8,0.3 -1.2,0.5c-0.1,0.1 -0.2,0.2 -0.2,0.4v3.2h-1v-12c0.7,0.2 1.5,0.5 2.2,0.8c0.1,0.1 0.2,0.2 0.2,0.4L21.8,14.5z" />
9 <path
10 android:fillColor="?attr/colorControlNormal"
11 android:pathData="M17,6.8C16.5,6.4 15.7,6 12,6S7.4,6.4 7,6.8C6.8,7.1 6.6,7.4 6.7,7.7v8.9C6.7,17.4 7.3,18 8,18h7.9c0.8,0 1.4,-0.6 1.4,-1.4l0,0V7.7C17.3,7.4 17.2,7.1 17,6.8zM16.2,15.9c0,0.6 -0.5,1.1 -1.1,1.1H8.9c-0.6,0 -1.1,-0.5 -1.1,-1.1V8.4c0,-0.3 0.1,-0.5 0.2,-0.8C8.4,7.3 9,7 12,7s3.6,0.3 4,0.7c0.2,0.2 0.2,0.5 0.2,0.8V15.9z" />
12</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_check.xml b/src/android/app/src/main/res/drawable/ic_check.xml
new file mode 100644
index 000000000..04b89abf2
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_check.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorOnSurface"
8 android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_check_circle.xml b/src/android/app/src/main/res/drawable/ic_check_circle.xml
new file mode 100644
index 000000000..49e6ecd71
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_check_circle.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_clear.xml b/src/android/app/src/main/res/drawable/ic_clear.xml
new file mode 100644
index 000000000..b6edb1d32
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_clear.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_controller.xml b/src/android/app/src/main/res/drawable/ic_controller.xml
new file mode 100644
index 000000000..060cd9ae2
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_controller.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportHeight="24"
5 android:viewportWidth="24">
6 <path
7 android:fillColor="?attr/colorOnSurface"
8 android:pathData="M21,6L3,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.1 -0.9,-2 -2,-2zM11,13L8,13v3L6,16v-3L3,13v-2h3L6,8h2v3h3v2zM15.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S18.67,9 19.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_diamond.xml b/src/android/app/src/main/res/drawable/ic_diamond.xml
new file mode 100644
index 000000000..3896e12e4
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_diamond.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M19,3H5L2,9l10,12L22,9L19,3zM9.62,8l1.5,-3h1.76l1.5,3H9.62zM11,10v6.68L5.44,10H11zM13,10h5.56L13,16.68V10zM19.26,8h-2.65l-1.5,-3h2.65L19.26,8zM6.24,5h2.65l-1.5,3H4.74L6.24,5z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_discord.xml b/src/android/app/src/main/res/drawable/ic_discord.xml
new file mode 100644
index 000000000..7a9c6ba79
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_discord.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="200dp"
3 android:height="200dp"
4 android:viewportWidth="256"
5 android:viewportHeight="256">
6 <path
7 android:fillColor="#5865F2"
8 android:fillType="nonZero"
9 android:pathData="M216.86,45.1C200.29,37.34 182.57,31.71 164.04,28.5C161.77,32.61 159.11,38.15 157.28,42.55C137.58,39.58 118.07,39.58 98.74,42.55C96.91,38.15 94.19,32.61 91.9,28.5C73.35,31.71 55.61,37.36 39.04,45.14C5.62,95.65 -3.44,144.9 1.09,193.46C23.26,210.01 44.74,220.07 65.86,226.65C71.08,219.47 75.73,211.84 79.74,203.8C72.1,200.9 64.79,197.32 57.89,193.17C59.72,191.81 61.51,190.39 63.24,188.93C105.37,208.63 151.13,208.63 192.75,188.93C194.51,190.39 196.3,191.81 198.11,193.17C191.18,197.34 183.85,200.92 176.22,203.82C180.23,211.84 184.86,219.49 190.1,226.67C211.24,220.09 232.74,210.03 254.91,193.46C260.23,137.17 245.83,88.37 216.86,45.1ZM85.47,163.59C72.83,163.59 62.46,151.79 62.46,137.41C62.46,123.04 72.61,111.21 85.47,111.21C98.34,111.21 108.71,123.02 108.49,137.41C108.51,151.79 98.34,163.59 85.47,163.59ZM170.53,163.59C157.88,163.59 147.51,151.79 147.51,137.41C147.51,123.04 157.66,111.21 170.53,111.21C183.39,111.21 193.76,123.02 193.54,137.41C193.54,151.79 183.39,163.59 170.53,163.59Z" />
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_exit.xml b/src/android/app/src/main/res/drawable/ic_exit.xml
new file mode 100644
index 000000000..a55a1d387
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_exit.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:autoMirrored="true"
5 android:viewportHeight="24"
6 android:viewportWidth="24">
7 <path
8 android:fillColor="?attr/colorControlNormal"
9 android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z" />
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_firmware.xml b/src/android/app/src/main/res/drawable/ic_firmware.xml
new file mode 100644
index 000000000..61f3485e4
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_firmware.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="960"
5 android:viewportHeight="960"
6 android:tint="?attr/colorControlNormal">
7 <path
8 android:fillColor="@android:color/white"
9 android:pathData="M160,840Q127,840 103.5,816.5Q80,793 80,760L80,200Q80,167 103.5,143.5Q127,120 160,120L720,120Q753,120 776.5,143.5Q800,167 800,200L800,280L840,280Q857,280 868.5,291.5Q880,303 880,320Q880,337 868.5,348.5Q857,360 840,360L800,360L800,440L840,440Q857,440 868.5,451.5Q880,463 880,480Q880,497 868.5,508.5Q857,520 840,520L800,520L800,600L840,600Q857,600 868.5,611.5Q880,623 880,640Q880,657 868.5,668.5Q857,680 840,680L800,680L800,760Q800,793 776.5,816.5Q753,840 720,840L160,840ZM160,760L720,760Q720,760 720,760Q720,760 720,760L720,200Q720,200 720,200Q720,200 720,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760ZM280,680L400,680Q417,680 428.5,668.5Q440,657 440,640L440,560Q440,543 428.5,531.5Q417,520 400,520L280,520Q263,520 251.5,531.5Q240,543 240,560L240,640Q240,657 251.5,668.5Q263,680 280,680ZM520,400L600,400Q617,400 628.5,388.5Q640,377 640,360L640,320Q640,303 628.5,291.5Q617,280 600,280L520,280Q503,280 491.5,291.5Q480,303 480,320L480,360Q480,377 491.5,388.5Q503,400 520,400ZM280,480L400,480Q417,480 428.5,468.5Q440,457 440,440L440,320Q440,303 428.5,291.5Q417,280 400,280L280,280Q263,280 251.5,291.5Q240,303 240,320L240,440Q240,457 251.5,468.5Q263,480 280,480ZM520,680L600,680Q617,680 628.5,668.5Q640,657 640,640L640,480Q640,463 628.5,451.5Q617,440 600,440L520,440Q503,440 491.5,451.5Q480,463 480,480L480,640Q480,657 491.5,668.5Q503,680 520,680ZM160,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760L160,760Q160,760 160,760Q160,760 160,760L160,200Q160,200 160,200Q160,200 160,200Z"/>
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_folder_open.xml b/src/android/app/src/main/res/drawable/ic_folder_open.xml
new file mode 100644
index 000000000..7958fdaec
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_folder_open.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M4.17,20.48C3.431,20.48 2.805,20.223 2.291,19.709C1.777,19.195 1.52,18.569 1.52,17.83V5.958C1.52,5.21 1.777,4.581 2.291,4.072C2.805,3.562 3.431,3.308 4.17,3.308H9.788L12,5.52H19.83C20.578,5.52 21.207,5.777 21.716,6.291C22.226,6.805 22.48,7.431 22.48,8.17H4.17V17.901L6.57,10.17H23.938L21.448,18.194C21.201,18.957 20.817,19.528 20.294,19.909C19.77,20.29 19.118,20.48 18.336,20.48H4.17Z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_github.xml b/src/android/app/src/main/res/drawable/ic_github.xml
new file mode 100644
index 000000000..c2ee43803
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_github.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="98dp"
3 android:height="96dp"
4 android:viewportWidth="98"
5 android:viewportHeight="96">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:fillType="evenOdd"
9 android:pathData="M48.854,0C21.839,0 0,22 0,49.217c0,21.756 13.993,40.172 33.405,46.69 2.427,0.49 3.316,-1.059 3.316,-2.362 0,-1.141 -0.08,-5.052 -0.08,-9.127 -13.59,2.934 -16.42,-5.867 -16.42,-5.867 -2.184,-5.704 -5.42,-7.17 -5.42,-7.17 -4.448,-3.015 0.324,-3.015 0.324,-3.015 4.934,0.326 7.523,5.052 7.523,5.052 4.367,7.496 11.404,5.378 14.235,4.074 0.404,-3.178 1.699,-5.378 3.074,-6.6 -10.839,-1.141 -22.243,-5.378 -22.243,-24.283 0,-5.378 1.94,-9.778 5.014,-13.2 -0.485,-1.222 -2.184,-6.275 0.486,-13.038 0,0 4.125,-1.304 13.426,5.052a46.97,46.97 0,0 1,12.214 -1.63c4.125,0 8.33,0.571 12.213,1.63 9.302,-6.356 13.427,-5.052 13.427,-5.052 2.67,6.763 0.97,11.816 0.485,13.038 3.155,3.422 5.015,7.822 5.015,13.2 0,18.905 -11.404,23.06 -22.324,24.283 1.78,1.548 3.316,4.481 3.316,9.126 0,6.6 -0.08,11.897 -0.08,13.526 0,1.304 0.89,2.853 3.316,2.364 19.412,-6.52 33.405,-24.935 33.405,-46.691C97.707,22 75.788,0 48.854,0z" />
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_icon_bg.xml b/src/android/app/src/main/res/drawable/ic_icon_bg.xml
new file mode 100644
index 000000000..df62dde92
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_icon_bg.xml
@@ -0,0 +1,751 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="512dp"
3 android:height="512dp"
4 android:viewportWidth="512"
5 android:viewportHeight="512">
6 <group>
7 <clip-path
8 android:pathData="M0,0h512v512h-512z"/>
9 <path
10 android:pathData="M0,0h512v512h-512z"
11 android:fillColor="#ffffff"/>
12 <path
13 android:pathData="M0,0h512v512h-512z"
14 android:fillColor="#1C1C1C"/>
15 <path
16 android:pathData="M208.16,7H159.88C155.54,7 152,10.54 152,14.88V92.16C152,96.54 155.54,100.04 159.88,100.04H208.12C212.5,100.04 216,96.5 216,92.16V14.88C216.04,10.54 212.5,7 208.16,7Z"
17 android:strokeAlpha="0.7"
18 android:fillColor="#282828"
19 android:fillAlpha="0.7"/>
20 <path
21 android:pathData="M208.8,89.73H158.44C156.65,89.73 155.18,88.26 155.18,86.47V17.02C155.18,15.23 156.65,13.76 158.44,13.76H208.84C210.63,13.76 212.1,15.23 212.1,17.02V86.51C212.06,88.26 210.59,89.73 208.8,89.73Z"
22 android:strokeAlpha="0.7"
23 android:fillColor="#1C1C1C"
24 android:fillAlpha="0.7"/>
25 <path
26 android:pathData="M194.16,14.16H173.08V12.93C173.08,12.29 173.6,11.77 174.24,11.77H193.01C193.65,11.77 194.16,12.29 194.16,12.93V14.16Z"
27 android:strokeAlpha="0.7"
28 android:fillColor="#1C1C1C"
29 android:fillAlpha="0.7"/>
30 <path
31 android:pathData="M183.86,97.29L177.93,92.92H189.79L183.86,97.29Z"
32 android:strokeAlpha="0.7"
33 android:fillColor="#1C1C1C"
34 android:fillAlpha="0.7"/>
35 <path
36 android:pathData="M424.16,7H375.88C371.54,7 368,10.54 368,14.88V92.16C368,96.54 371.54,100.04 375.88,100.04H424.12C428.5,100.04 432,96.5 432,92.16V14.88C432.04,10.54 428.5,7 424.16,7Z"
37 android:strokeAlpha="0.7"
38 android:fillColor="#282828"
39 android:fillAlpha="0.7"/>
40 <path
41 android:pathData="M424.8,89.73H374.44C372.65,89.73 371.18,88.26 371.18,86.47V17.02C371.18,15.23 372.65,13.76 374.44,13.76H424.84C426.63,13.76 428.1,15.23 428.1,17.02V86.51C428.06,88.26 426.59,89.73 424.8,89.73Z"
42 android:strokeAlpha="0.7"
43 android:fillColor="#1C1C1C"
44 android:fillAlpha="0.7"/>
45 <path
46 android:pathData="M410.16,14.16H389.08V12.93C389.08,12.29 389.6,11.77 390.23,11.77H409.01C409.65,11.77 410.16,12.29 410.16,12.93V14.16Z"
47 android:strokeAlpha="0.7"
48 android:fillColor="#1C1C1C"
49 android:fillAlpha="0.7"/>
50 <path
51 android:pathData="M399.86,97.29L393.93,92.92H405.79L399.86,97.29Z"
52 android:strokeAlpha="0.7"
53 android:fillColor="#1C1C1C"
54 android:fillAlpha="0.7"/>
55 <path
56 android:pathData="M352.16,109H303.88C299.54,109 296,112.54 296,116.88V194.16C296,198.54 299.54,202.04 303.88,202.04H352.12C356.5,202.04 360,198.5 360,194.16V116.88C360.04,112.54 356.5,109 352.16,109Z"
57 android:strokeAlpha="0.7"
58 android:fillColor="#282828"
59 android:fillAlpha="0.7"/>
60 <path
61 android:pathData="M352.8,191.73H302.44C300.65,191.73 299.18,190.26 299.18,188.47V119.02C299.18,117.23 300.65,115.76 302.44,115.76H352.84C354.63,115.76 356.1,117.23 356.1,119.02V188.51C356.06,190.26 354.59,191.73 352.8,191.73Z"
62 android:strokeAlpha="0.7"
63 android:fillColor="#1C1C1C"
64 android:fillAlpha="0.7"/>
65 <path
66 android:pathData="M338.16,116.16H317.08V114.93C317.08,114.29 317.6,113.77 318.23,113.77H337.01C337.65,113.77 338.16,114.29 338.16,114.93V116.16Z"
67 android:strokeAlpha="0.7"
68 android:fillColor="#1C1C1C"
69 android:fillAlpha="0.7"/>
70 <path
71 android:pathData="M327.86,199.29L321.93,194.92H333.79L327.86,199.29Z"
72 android:strokeAlpha="0.7"
73 android:fillColor="#1C1C1C"
74 android:fillAlpha="0.7"/>
75 <path
76 android:pathData="M496.16,7H447.88C443.54,7 440,10.54 440,14.88V92.16C440,96.54 443.54,100.04 447.88,100.04H496.12C500.5,100.04 504,96.5 504,92.16V14.88C504.04,10.54 500.5,7 496.16,7Z"
77 android:strokeAlpha="0.7"
78 android:fillColor="#282828"
79 android:fillAlpha="0.7"/>
80 <path
81 android:pathData="M496.8,89.73H446.44C444.65,89.73 443.18,88.26 443.18,86.47V17.02C443.18,15.23 444.65,13.76 446.44,13.76H496.84C498.63,13.76 500.1,15.23 500.1,17.02V86.51C500.06,88.26 498.59,89.73 496.8,89.73Z"
82 android:strokeAlpha="0.7"
83 android:fillColor="#1C1C1C"
84 android:fillAlpha="0.7"/>
85 <path
86 android:pathData="M482.16,14.16H461.08V12.93C461.08,12.29 461.6,11.77 462.23,11.77H481.01C481.65,11.77 482.16,12.29 482.16,12.93V14.16Z"
87 android:strokeAlpha="0.7"
88 android:fillColor="#1C1C1C"
89 android:fillAlpha="0.7"/>
90 <path
91 android:pathData="M471.86,97.29L465.93,92.92H477.79L471.86,97.29Z"
92 android:strokeAlpha="0.7"
93 android:fillColor="#1C1C1C"
94 android:fillAlpha="0.7"/>
95 <path
96 android:pathData="M352.16,7H303.88C299.54,7 296,10.54 296,14.88V92.16C296,96.54 299.54,100.04 303.88,100.04H352.12C356.5,100.04 360,96.5 360,92.16V14.88C360.04,10.54 356.5,7 352.16,7Z"
97 android:strokeAlpha="0.7"
98 android:fillColor="#282828"
99 android:fillAlpha="0.7"/>
100 <path
101 android:pathData="M352.8,89.73H302.44C300.65,89.73 299.18,88.26 299.18,86.47V17.02C299.18,15.23 300.65,13.76 302.44,13.76H352.84C354.63,13.76 356.1,15.23 356.1,17.02V86.51C356.06,88.26 354.59,89.73 352.8,89.73Z"
102 android:strokeAlpha="0.7"
103 android:fillColor="#1C1C1C"
104 android:fillAlpha="0.7"/>
105 <path
106 android:pathData="M338.16,14.16H317.08V12.93C317.08,12.29 317.6,11.77 318.23,11.77H337.01C337.65,11.77 338.16,12.29 338.16,12.93V14.16Z"
107 android:strokeAlpha="0.7"
108 android:fillColor="#1C1C1C"
109 android:fillAlpha="0.7"/>
110 <path
111 android:pathData="M327.86,97.29L321.93,92.92H333.79L327.86,97.29Z"
112 android:strokeAlpha="0.7"
113 android:fillColor="#1C1C1C"
114 android:fillAlpha="0.7"/>
115 <path
116 android:pathData="M280.16,7H231.88C227.54,7 224,10.54 224,14.88V92.16C224,96.54 227.54,100.04 231.88,100.04H280.12C284.5,100.04 288,96.5 288,92.16V14.88C288.04,10.54 284.5,7 280.16,7Z"
117 android:strokeAlpha="0.7"
118 android:fillColor="#282828"
119 android:fillAlpha="0.7"/>
120 <path
121 android:pathData="M280.8,89.73H230.44C228.65,89.73 227.18,88.26 227.18,86.47V17.02C227.18,15.23 228.65,13.76 230.44,13.76H280.84C282.63,13.76 284.1,15.23 284.1,17.02V86.51C284.06,88.26 282.59,89.73 280.8,89.73Z"
122 android:strokeAlpha="0.7"
123 android:fillColor="#1C1C1C"
124 android:fillAlpha="0.7"/>
125 <path
126 android:pathData="M266.16,14.16H245.08V12.93C245.08,12.29 245.6,11.77 246.24,11.77H265.01C265.65,11.77 266.16,12.29 266.16,12.93V14.16Z"
127 android:strokeAlpha="0.7"
128 android:fillColor="#1C1C1C"
129 android:fillAlpha="0.7"/>
130 <path
131 android:pathData="M255.86,97.29L249.93,92.92H261.79L255.86,97.29Z"
132 android:strokeAlpha="0.7"
133 android:fillColor="#1C1C1C"
134 android:fillAlpha="0.7"/>
135 <path
136 android:pathData="M424.16,109H375.88C371.54,109 368,112.54 368,116.88V194.16C368,198.54 371.54,202.04 375.88,202.04H424.12C428.5,202.04 432,198.5 432,194.16V116.88C432.04,112.54 428.5,109 424.16,109Z"
137 android:strokeAlpha="0.7"
138 android:fillColor="#282828"
139 android:fillAlpha="0.7"/>
140 <path
141 android:pathData="M135.16,411H86.88C82.54,411 79,414.54 79,418.88V496.16C79,500.54 82.54,504.04 86.88,504.04H135.12C139.5,504.04 143,500.5 143,496.16V418.88C143.04,414.54 139.5,411 135.16,411Z"
142 android:strokeAlpha="0.7"
143 android:fillColor="#282828"
144 android:fillAlpha="0.7"/>
145 <path
146 android:pathData="M64.16,7H15.88C11.54,7 8,10.54 8,14.88V92.16C8,96.54 11.54,100.04 15.88,100.04H64.12C68.5,100.04 72,96.5 72,92.16V14.88C72.04,10.54 68.5,7 64.16,7Z"
147 android:strokeAlpha="0.7"
148 android:fillColor="#282828"
149 android:fillAlpha="0.7"/>
150 <path
151 android:pathData="M64.8,89.73H14.44C12.65,89.73 11.18,88.26 11.18,86.47V17.02C11.18,15.23 12.65,13.76 14.44,13.76H64.84C66.63,13.76 68.1,15.23 68.1,17.02V86.51C68.06,88.26 66.59,89.73 64.8,89.73Z"
152 android:strokeAlpha="0.7"
153 android:fillColor="#1C1C1C"
154 android:fillAlpha="0.7"/>
155 <path
156 android:pathData="M50.16,14.16H29.08V12.93C29.08,12.29 29.6,11.77 30.23,11.77H49.01C49.65,11.77 50.16,12.29 50.16,12.93V14.16Z"
157 android:strokeAlpha="0.7"
158 android:fillColor="#1C1C1C"
159 android:fillAlpha="0.7"/>
160 <path
161 android:pathData="M39.86,97.29L33.93,92.92H45.79L39.86,97.29Z"
162 android:strokeAlpha="0.7"
163 android:fillColor="#1C1C1C"
164 android:fillAlpha="0.7"/>
165 <path
166 android:pathData="M63.16,310H14.88C10.54,310 7,313.54 7,317.88V395.16C7,399.54 10.54,403.04 14.88,403.04H63.12C67.5,403.04 71,399.5 71,395.16V317.88C71.04,313.54 67.5,310 63.16,310Z"
167 android:strokeAlpha="0.7"
168 android:fillColor="#282828"
169 android:fillAlpha="0.7"/>
170 <path
171 android:pathData="M63.8,392.73H13.44C11.65,392.73 10.18,391.26 10.18,389.47V320.02C10.18,318.23 11.65,316.76 13.44,316.76H63.84C65.63,316.76 67.1,318.23 67.1,320.02V389.51C67.06,391.26 65.59,392.73 63.8,392.73Z"
172 android:strokeAlpha="0.7"
173 android:fillColor="#1C1C1C"
174 android:fillAlpha="0.7"/>
175 <path
176 android:pathData="M49.16,317.16H28.08V315.93C28.08,315.29 28.6,314.77 29.23,314.77H48.01C48.65,314.77 49.16,315.29 49.16,315.93V317.16Z"
177 android:strokeAlpha="0.7"
178 android:fillColor="#1C1C1C"
179 android:fillAlpha="0.7"/>
180 <path
181 android:pathData="M38.86,400.29L32.93,395.92H44.79L38.86,400.29Z"
182 android:strokeAlpha="0.7"
183 android:fillColor="#1C1C1C"
184 android:fillAlpha="0.7"/>
185 <path
186 android:pathData="M424.16,209H375.88C371.54,209 368,212.54 368,216.88V294.16C368,298.54 371.54,302.04 375.88,302.04H424.12C428.5,302.04 432,298.5 432,294.16V216.88C432.04,212.54 428.5,209 424.16,209Z"
187 android:strokeAlpha="0.7"
188 android:fillColor="#282828"
189 android:fillAlpha="0.7"/>
190 <path
191 android:pathData="M424.8,291.73H374.44C372.65,291.73 371.18,290.26 371.18,288.47V219.02C371.18,217.23 372.65,215.76 374.44,215.76H424.84C426.63,215.76 428.1,217.23 428.1,219.02V288.51C428.06,290.26 426.59,291.73 424.8,291.73Z"
192 android:strokeAlpha="0.7"
193 android:fillColor="#1C1C1C"
194 android:fillAlpha="0.7"/>
195 <path
196 android:pathData="M410.16,216.16H389.08V214.93C389.08,214.29 389.6,213.77 390.23,213.77H409.01C409.65,213.77 410.16,214.29 410.16,214.93V216.16Z"
197 android:strokeAlpha="0.7"
198 android:fillColor="#1C1C1C"
199 android:fillAlpha="0.7"/>
200 <path
201 android:pathData="M399.86,299.29L393.93,294.92H405.79L399.86,299.29Z"
202 android:strokeAlpha="0.7"
203 android:fillColor="#1C1C1C"
204 android:fillAlpha="0.7"/>
205 <path
206 android:pathData="M496.16,209H447.88C443.54,209 440,212.54 440,216.88V294.16C440,298.54 443.54,302.04 447.88,302.04H496.12C500.5,302.04 504,298.5 504,294.16V216.88C504.04,212.54 500.5,209 496.16,209Z"
207 android:strokeAlpha="0.7"
208 android:fillColor="#282828"
209 android:fillAlpha="0.7"/>
210 <path
211 android:pathData="M496.8,291.73H446.44C444.65,291.73 443.18,290.26 443.18,288.47V219.02C443.18,217.23 444.65,215.76 446.44,215.76H496.84C498.63,215.76 500.1,217.23 500.1,219.02V288.51C500.06,290.26 498.59,291.73 496.8,291.73Z"
212 android:strokeAlpha="0.7"
213 android:fillColor="#1C1C1C"
214 android:fillAlpha="0.7"/>
215 <path
216 android:pathData="M482.16,216.16H461.08V214.93C461.08,214.29 461.6,213.77 462.23,213.77H481.01C481.65,213.77 482.16,214.29 482.16,214.93V216.16Z"
217 android:strokeAlpha="0.7"
218 android:fillColor="#1C1C1C"
219 android:fillAlpha="0.7"/>
220 <path
221 android:pathData="M471.86,299.29L465.93,294.92H477.79L471.86,299.29Z"
222 android:strokeAlpha="0.7"
223 android:fillColor="#1C1C1C"
224 android:fillAlpha="0.7"/>
225 <path
226 android:pathData="M136.16,209H87.88C83.54,209 80,212.54 80,216.88V294.16C80,298.54 83.54,302.04 87.88,302.04H136.12C140.5,302.04 144,298.5 144,294.16V216.88C144.04,212.54 140.5,209 136.16,209Z"
227 android:strokeAlpha="0.7"
228 android:fillColor="#282828"
229 android:fillAlpha="0.7"/>
230 <path
231 android:pathData="M136.8,291.73H86.44C84.65,291.73 83.18,290.26 83.18,288.47V219.02C83.18,217.23 84.65,215.76 86.44,215.76H136.84C138.63,215.76 140.1,217.23 140.1,219.02V288.51C140.06,290.26 138.59,291.73 136.8,291.73Z"
232 android:strokeAlpha="0.7"
233 android:fillColor="#1C1C1C"
234 android:fillAlpha="0.7"/>
235 <path
236 android:pathData="M122.16,216.16H101.08V214.93C101.08,214.29 101.6,213.77 102.24,213.77H121.01C121.65,213.77 122.16,214.29 122.16,214.93V216.16Z"
237 android:strokeAlpha="0.7"
238 android:fillColor="#1C1C1C"
239 android:fillAlpha="0.7"/>
240 <path
241 android:pathData="M111.86,299.29L105.93,294.92H117.79L111.86,299.29Z"
242 android:strokeAlpha="0.7"
243 android:fillColor="#1C1C1C"
244 android:fillAlpha="0.7"/>
245 <path
246 android:pathData="M352.16,209H303.88C299.54,209 296,212.54 296,216.88V294.16C296,298.54 299.54,302.04 303.88,302.04H352.12C356.5,302.04 360,298.5 360,294.16V216.88C360.04,212.54 356.5,209 352.16,209Z"
247 android:strokeAlpha="0.7"
248 android:fillColor="#282828"
249 android:fillAlpha="0.7"/>
250 <path
251 android:pathData="M352.8,291.73H302.44C300.65,291.73 299.18,290.26 299.18,288.47V219.02C299.18,217.23 300.65,215.76 302.44,215.76H352.84C354.63,215.76 356.1,217.23 356.1,219.02V288.51C356.06,290.26 354.59,291.73 352.8,291.73Z"
252 android:strokeAlpha="0.7"
253 android:fillColor="#1C1C1C"
254 android:fillAlpha="0.7"/>
255 <path
256 android:pathData="M338.16,216.16H317.08V214.93C317.08,214.29 317.6,213.77 318.23,213.77H337.01C337.65,213.77 338.16,214.29 338.16,214.93V216.16Z"
257 android:strokeAlpha="0.7"
258 android:fillColor="#1C1C1C"
259 android:fillAlpha="0.7"/>
260 <path
261 android:pathData="M327.86,299.29L321.93,294.92H333.79L327.86,299.29Z"
262 android:strokeAlpha="0.7"
263 android:fillColor="#1C1C1C"
264 android:fillAlpha="0.7"/>
265 <path
266 android:pathData="M64.16,209H15.88C11.54,209 8,212.54 8,216.88V294.16C8,298.54 11.54,302.04 15.88,302.04H64.12C68.5,302.04 72,298.5 72,294.16V216.88C72.04,212.54 68.5,209 64.16,209Z"
267 android:strokeAlpha="0.7"
268 android:fillColor="#282828"
269 android:fillAlpha="0.7"/>
270 <path
271 android:pathData="M64.8,291.73H14.44C12.65,291.73 11.18,290.26 11.18,288.47V219.02C11.18,217.23 12.65,215.76 14.44,215.76H64.84C66.63,215.76 68.1,217.23 68.1,219.02V288.51C68.06,290.26 66.59,291.73 64.8,291.73Z"
272 android:strokeAlpha="0.7"
273 android:fillColor="#1C1C1C"
274 android:fillAlpha="0.7"/>
275 <path
276 android:pathData="M50.16,216.16H29.08V214.93C29.08,214.29 29.6,213.77 30.23,213.77H49.01C49.65,213.77 50.16,214.29 50.16,214.93V216.16Z"
277 android:strokeAlpha="0.7"
278 android:fillColor="#1C1C1C"
279 android:fillAlpha="0.7"/>
280 <path
281 android:pathData="M39.86,299.29L33.93,294.92H45.79L39.86,299.29Z"
282 android:strokeAlpha="0.7"
283 android:fillColor="#1C1C1C"
284 android:fillAlpha="0.7"/>
285 <path
286 android:pathData="M135.16,310H86.88C82.54,310 79,313.54 79,317.88V395.16C79,399.54 82.54,403.04 86.88,403.04H135.12C139.5,403.04 143,399.5 143,395.16V317.88C143.04,313.54 139.5,310 135.16,310Z"
287 android:strokeAlpha="0.7"
288 android:fillColor="#282828"
289 android:fillAlpha="0.7"/>
290 <path
291 android:pathData="M135.8,392.73H85.44C83.65,392.73 82.18,391.26 82.18,389.47V320.02C82.18,318.23 83.65,316.76 85.44,316.76H135.84C137.63,316.76 139.1,318.23 139.1,320.02V389.51C139.06,391.26 137.59,392.73 135.8,392.73Z"
292 android:strokeAlpha="0.7"
293 android:fillColor="#1C1C1C"
294 android:fillAlpha="0.7"/>
295 <path
296 android:pathData="M121.16,317.16H100.08V315.93C100.08,315.29 100.6,314.77 101.24,314.77H120.01C120.65,314.77 121.16,315.29 121.16,315.93V317.16Z"
297 android:strokeAlpha="0.7"
298 android:fillColor="#1C1C1C"
299 android:fillAlpha="0.7"/>
300 <path
301 android:pathData="M110.86,400.29L104.93,395.92H116.79L110.86,400.29Z"
302 android:strokeAlpha="0.7"
303 android:fillColor="#1C1C1C"
304 android:fillAlpha="0.7"/>
305 <path
306 android:pathData="M208.16,108H159.88C155.54,108 152,111.54 152,115.88V193.16C152,197.54 155.54,201.04 159.88,201.04H208.12C212.5,201.04 216,197.5 216,193.16V115.88C216.04,111.54 212.5,108 208.16,108Z"
307 android:strokeAlpha="0.7"
308 android:fillColor="#282828"
309 android:fillAlpha="0.7"/>
310 <path
311 android:pathData="M208.8,190.73H158.44C156.65,190.73 155.18,189.26 155.18,187.47V118.02C155.18,116.23 156.65,114.76 158.44,114.76H208.84C210.63,114.76 212.1,116.23 212.1,118.02V187.51C212.06,189.26 210.59,190.73 208.8,190.73Z"
312 android:strokeAlpha="0.7"
313 android:fillColor="#1C1C1C"
314 android:fillAlpha="0.7"/>
315 <path
316 android:pathData="M194.16,115.16H173.08V113.93C173.08,113.29 173.6,112.77 174.24,112.77H193.01C193.65,112.77 194.16,113.29 194.16,113.93V115.16Z"
317 android:strokeAlpha="0.7"
318 android:fillColor="#1C1C1C"
319 android:fillAlpha="0.7"/>
320 <path
321 android:pathData="M183.86,198.29L177.93,193.92H189.79L183.86,198.29Z"
322 android:strokeAlpha="0.7"
323 android:fillColor="#1C1C1C"
324 android:fillAlpha="0.7"/>
325 <path
326 android:pathData="M496.16,108H447.88C443.54,108 440,111.54 440,115.88V193.16C440,197.54 443.54,201.04 447.88,201.04H496.12C500.5,201.04 504,197.5 504,193.16V115.88C504.04,111.54 500.5,108 496.16,108Z"
327 android:strokeAlpha="0.7"
328 android:fillColor="#282828"
329 android:fillAlpha="0.7"/>
330 <path
331 android:pathData="M496.8,190.73H446.44C444.65,190.73 443.18,189.26 443.18,187.47V118.02C443.18,116.23 444.65,114.76 446.44,114.76H496.84C498.63,114.76 500.1,116.23 500.1,118.02V187.51C500.06,189.26 498.59,190.73 496.8,190.73Z"
332 android:strokeAlpha="0.7"
333 android:fillColor="#1C1C1C"
334 android:fillAlpha="0.7"/>
335 <path
336 android:pathData="M482.16,115.16H461.08V113.93C461.08,113.29 461.6,112.77 462.23,112.77H481.01C481.65,112.77 482.16,113.29 482.16,113.93V115.16Z"
337 android:strokeAlpha="0.7"
338 android:fillColor="#1C1C1C"
339 android:fillAlpha="0.7"/>
340 <path
341 android:pathData="M471.86,198.29L465.93,193.92H477.79L471.86,198.29Z"
342 android:strokeAlpha="0.7"
343 android:fillColor="#1C1C1C"
344 android:fillAlpha="0.7"/>
345 <path
346 android:pathData="M64.16,108H15.88C11.54,108 8,111.54 8,115.88V193.16C8,197.54 11.54,201.04 15.88,201.04H64.12C68.5,201.04 72,197.5 72,193.16V115.88C72.04,111.54 68.5,108 64.16,108Z"
347 android:strokeAlpha="0.7"
348 android:fillColor="#282828"
349 android:fillAlpha="0.7"/>
350 <path
351 android:pathData="M64.8,190.73H14.44C12.65,190.73 11.18,189.26 11.18,187.47V118.02C11.18,116.23 12.65,114.76 14.44,114.76H64.84C66.63,114.76 68.1,116.23 68.1,118.02V187.51C68.06,189.26 66.59,190.73 64.8,190.73Z"
352 android:strokeAlpha="0.7"
353 android:fillColor="#1C1C1C"
354 android:fillAlpha="0.7"/>
355 <path
356 android:pathData="M50.16,115.16H29.08V113.93C29.08,113.29 29.6,112.77 30.23,112.77H49.01C49.65,112.77 50.16,113.29 50.16,113.93V115.16Z"
357 android:strokeAlpha="0.7"
358 android:fillColor="#1C1C1C"
359 android:fillAlpha="0.7"/>
360 <path
361 android:pathData="M39.86,198.29L33.93,193.92H45.79L39.86,198.29Z"
362 android:strokeAlpha="0.7"
363 android:fillColor="#1C1C1C"
364 android:fillAlpha="0.7"/>
365 <path
366 android:pathData="M280.16,108H231.88C227.54,108 224,111.54 224,115.88V193.16C224,197.54 227.54,201.04 231.88,201.04H280.12C284.5,201.04 288,197.5 288,193.16V115.88C288.04,111.54 284.5,108 280.16,108Z"
367 android:strokeAlpha="0.7"
368 android:fillColor="#282828"
369 android:fillAlpha="0.7"/>
370 <path
371 android:pathData="M280.8,190.73H230.44C228.65,190.73 227.18,189.26 227.18,187.47V118.02C227.18,116.23 228.65,114.76 230.44,114.76H280.84C282.63,114.76 284.1,116.23 284.1,118.02V187.51C284.06,189.26 282.59,190.73 280.8,190.73Z"
372 android:strokeAlpha="0.7"
373 android:fillColor="#1C1C1C"
374 android:fillAlpha="0.7"/>
375 <path
376 android:pathData="M266.16,115.16H245.08V113.93C245.08,113.29 245.6,112.77 246.24,112.77H265.01C265.65,112.77 266.16,113.29 266.16,113.93V115.16Z"
377 android:strokeAlpha="0.7"
378 android:fillColor="#1C1C1C"
379 android:fillAlpha="0.7"/>
380 <path
381 android:pathData="M255.86,198.29L249.93,193.92H261.79L255.86,198.29Z"
382 android:strokeAlpha="0.7"
383 android:fillColor="#1C1C1C"
384 android:fillAlpha="0.7"/>
385 <path
386 android:pathData="M496.16,310H447.88C443.54,310 440,313.54 440,317.88V395.16C440,399.54 443.54,403.04 447.88,403.04H496.12C500.5,403.04 504,399.5 504,395.16V317.88C504.04,313.54 500.5,310 496.16,310Z"
387 android:strokeAlpha="0.7"
388 android:fillColor="#282828"
389 android:fillAlpha="0.7"/>
390 <path
391 android:pathData="M496.8,392.73H446.44C444.65,392.73 443.18,391.26 443.18,389.47V320.02C443.18,318.23 444.65,316.76 446.44,316.76H496.84C498.63,316.76 500.1,318.23 500.1,320.02V389.51C500.06,391.26 498.59,392.73 496.8,392.73Z"
392 android:strokeAlpha="0.7"
393 android:fillColor="#1C1C1C"
394 android:fillAlpha="0.7"/>
395 <path
396 android:pathData="M482.16,317.16H461.08V315.93C461.08,315.29 461.6,314.77 462.23,314.77H481.01C481.65,314.77 482.16,315.29 482.16,315.93V317.16Z"
397 android:strokeAlpha="0.7"
398 android:fillColor="#1C1C1C"
399 android:fillAlpha="0.7"/>
400 <path
401 android:pathData="M471.86,400.29L465.93,395.92H477.79L471.86,400.29Z"
402 android:strokeAlpha="0.7"
403 android:fillColor="#1C1C1C"
404 android:fillAlpha="0.7"/>
405 <path
406 android:pathData="M352.16,310H303.88C299.54,310 296,313.54 296,317.88V395.16C296,399.54 299.54,403.04 303.88,403.04H352.12C356.5,403.04 360,399.5 360,395.16V317.88C360.04,313.54 356.5,310 352.16,310Z"
407 android:strokeAlpha="0.7"
408 android:fillColor="#282828"
409 android:fillAlpha="0.7"/>
410 <path
411 android:pathData="M352.8,392.73H302.44C300.65,392.73 299.18,391.26 299.18,389.47V320.02C299.18,318.23 300.65,316.76 302.44,316.76H352.84C354.63,316.76 356.1,318.23 356.1,320.02V389.51C356.06,391.26 354.59,392.73 352.8,392.73Z"
412 android:strokeAlpha="0.7"
413 android:fillColor="#1C1C1C"
414 android:fillAlpha="0.7"/>
415 <path
416 android:pathData="M338.16,317.16H317.08V315.93C317.08,315.29 317.6,314.77 318.23,314.77H337.01C337.65,314.77 338.16,315.29 338.16,315.93V317.16Z"
417 android:strokeAlpha="0.7"
418 android:fillColor="#1C1C1C"
419 android:fillAlpha="0.7"/>
420 <path
421 android:pathData="M327.86,400.29L321.93,395.92H333.79L327.86,400.29Z"
422 android:strokeAlpha="0.7"
423 android:fillColor="#1C1C1C"
424 android:fillAlpha="0.7"/>
425 <path
426 android:pathData="M63.16,411H14.88C10.54,411 7,414.54 7,418.88V496.16C7,500.54 10.54,504.04 14.88,504.04H63.12C67.5,504.04 71,500.5 71,496.16V418.88C71.04,414.54 67.5,411 63.16,411Z"
427 android:strokeAlpha="0.7"
428 android:fillColor="#282828"
429 android:fillAlpha="0.7"/>
430 <path
431 android:pathData="M63.8,493.73H13.44C11.65,493.73 10.18,492.26 10.18,490.47V421.02C10.18,419.23 11.65,417.76 13.44,417.76H63.84C65.63,417.76 67.1,419.23 67.1,421.02V490.51C67.06,492.26 65.59,493.73 63.8,493.73Z"
432 android:strokeAlpha="0.7"
433 android:fillColor="#1C1C1C"
434 android:fillAlpha="0.7"/>
435 <path
436 android:pathData="M49.16,418.16H28.08V416.93C28.08,416.29 28.6,415.77 29.23,415.77H48.01C48.65,415.77 49.16,416.29 49.16,416.93V418.16Z"
437 android:strokeAlpha="0.7"
438 android:fillColor="#1C1C1C"
439 android:fillAlpha="0.7"/>
440 <path
441 android:pathData="M38.86,501.29L32.93,496.92H44.79L38.86,501.29Z"
442 android:strokeAlpha="0.7"
443 android:fillColor="#1C1C1C"
444 android:fillAlpha="0.7"/>
445 <path
446 android:pathData="M496.16,411H447.88C443.54,411 440,414.54 440,418.88V496.16C440,500.54 443.54,504.04 447.88,504.04H496.12C500.5,504.04 504,500.5 504,496.16V418.88C504.04,414.54 500.5,411 496.16,411Z"
447 android:strokeAlpha="0.7"
448 android:fillColor="#282828"
449 android:fillAlpha="0.7"/>
450 <path
451 android:pathData="M496.8,493.73H446.44C444.65,493.73 443.18,492.26 443.18,490.47V421.02C443.18,419.23 444.65,417.76 446.44,417.76H496.84C498.63,417.76 500.1,419.23 500.1,421.02V490.51C500.06,492.26 498.59,493.73 496.8,493.73Z"
452 android:strokeAlpha="0.7"
453 android:fillColor="#1C1C1C"
454 android:fillAlpha="0.7"/>
455 <path
456 android:pathData="M482.16,418.16H461.08V416.93C461.08,416.29 461.6,415.77 462.23,415.77H481.01C481.65,415.77 482.16,416.29 482.16,416.93V418.16Z"
457 android:strokeAlpha="0.7"
458 android:fillColor="#1C1C1C"
459 android:fillAlpha="0.7"/>
460 <path
461 android:pathData="M471.86,501.29L465.93,496.92H477.79L471.86,501.29Z"
462 android:strokeAlpha="0.7"
463 android:fillColor="#1C1C1C"
464 android:fillAlpha="0.7"/>
465 <path
466 android:pathData="M352.16,411H303.88C299.54,411 296,414.54 296,418.88V496.16C296,500.54 299.54,504.04 303.88,504.04H352.12C356.5,504.04 360,500.5 360,496.16V418.88C360.04,414.54 356.5,411 352.16,411Z"
467 android:strokeAlpha="0.7"
468 android:fillColor="#282828"
469 android:fillAlpha="0.7"/>
470 <path
471 android:pathData="M352.8,493.73H302.44C300.65,493.73 299.18,492.26 299.18,490.47V421.02C299.18,419.23 300.65,417.76 302.44,417.76H352.84C354.63,417.76 356.1,419.23 356.1,421.02V490.51C356.06,492.26 354.59,493.73 352.8,493.73Z"
472 android:strokeAlpha="0.7"
473 android:fillColor="#1C1C1C"
474 android:fillAlpha="0.7"/>
475 <path
476 android:pathData="M338.16,418.16H317.08V416.93C317.08,416.29 317.6,415.77 318.23,415.77H337.01C337.65,415.77 338.16,416.29 338.16,416.93V418.16Z"
477 android:strokeAlpha="0.7"
478 android:fillColor="#1C1C1C"
479 android:fillAlpha="0.7"/>
480 <path
481 android:pathData="M327.86,501.29L321.93,496.92H333.79L327.86,501.29Z"
482 android:strokeAlpha="0.7"
483 android:fillColor="#1C1C1C"
484 android:fillAlpha="0.7"/>
485 <path
486 android:pathData="M208.16,411H159.88C155.54,411 152,414.54 152,418.88V496.16C152,500.54 155.54,504.04 159.88,504.04H208.12C212.5,504.04 216,500.5 216,496.16V418.88C216.04,414.54 212.5,411 208.16,411Z"
487 android:strokeAlpha="0.7"
488 android:fillColor="#282828"
489 android:fillAlpha="0.7"/>
490 <path
491 android:pathData="M208.8,493.73H158.44C156.65,493.73 155.18,492.26 155.18,490.47V421.02C155.18,419.23 156.65,417.76 158.44,417.76H208.84C210.63,417.76 212.1,419.23 212.1,421.02V490.51C212.06,492.26 210.59,493.73 208.8,493.73Z"
492 android:strokeAlpha="0.7"
493 android:fillColor="#1C1C1C"
494 android:fillAlpha="0.7"/>
495 <path
496 android:pathData="M194.16,418.16H173.08V416.93C173.08,416.29 173.6,415.77 174.24,415.77H193.01C193.65,415.77 194.16,416.29 194.16,416.93V418.16Z"
497 android:strokeAlpha="0.7"
498 android:fillColor="#1C1C1C"
499 android:fillAlpha="0.7"/>
500 <path
501 android:pathData="M183.86,501.29L177.93,496.92H189.79L183.86,501.29Z"
502 android:strokeAlpha="0.7"
503 android:fillColor="#1C1C1C"
504 android:fillAlpha="0.7"/>
505 <path
506 android:pathData="M280.16,411H231.88C227.54,411 224,414.54 224,418.88V496.16C224,500.54 227.54,504.04 231.88,504.04H280.12C284.5,504.04 288,500.5 288,496.16V418.88C288.04,414.54 284.5,411 280.16,411Z"
507 android:strokeAlpha="0.7"
508 android:fillColor="#282828"
509 android:fillAlpha="0.7"/>
510 <path
511 android:pathData="M280.8,493.73H230.44C228.65,493.73 227.18,492.26 227.18,490.47V421.02C227.18,419.23 228.65,417.76 230.44,417.76H280.84C282.63,417.76 284.1,419.23 284.1,421.02V490.51C284.06,492.26 282.59,493.73 280.8,493.73Z"
512 android:strokeAlpha="0.7"
513 android:fillColor="#1C1C1C"
514 android:fillAlpha="0.7"/>
515 <path
516 android:pathData="M266.16,418.16H245.08V416.93C245.08,416.29 245.6,415.77 246.24,415.77H265.01C265.65,415.77 266.16,416.29 266.16,416.93V418.16Z"
517 android:strokeAlpha="0.7"
518 android:fillColor="#1C1C1C"
519 android:fillAlpha="0.7"/>
520 <path
521 android:pathData="M255.86,501.29L249.93,496.92H261.79L255.86,501.29Z"
522 android:strokeAlpha="0.7"
523 android:fillColor="#1C1C1C"
524 android:fillAlpha="0.7"/>
525 <group>
526 <clip-path
527 android:pathData="M80,8h64v192h-64z"/>
528 <path
529 android:pathData="M112.06,8H144.11V200H112.06C94.32,200 80,185.68 80,167.96V40.04C80,22.31 94.32,8 112.06,8Z"
530 android:strokeAlpha="0.7"
531 android:fillColor="#282828"
532 android:fillAlpha="0.7"/>
533 <path
534 android:pathData="M138.2,26.4H128.43C128.31,26.4 128.31,26.29 128.31,26.18V23.79C128.31,23.68 128.43,23.56 128.43,23.56H138.2C138.32,23.56 138.32,23.68 138.32,23.79V26.18C138.32,26.29 138.2,26.4 138.2,26.4Z"
535 android:strokeAlpha="0.7"
536 android:fillColor="#1C1C1C"
537 android:fillAlpha="0.7"/>
538 <path
539 android:pathData="M129.9,142.85V147.63C129.9,149.67 128.31,151.26 126.27,151.26H121.49C119.45,151.26 117.85,149.67 117.85,147.63V142.85C117.85,140.81 119.45,139.22 121.49,139.22H126.27C128.31,139.33 129.9,140.92 129.9,142.85Z"
540 android:strokeAlpha="0.7"
541 android:fillColor="#1C1C1C"
542 android:fillAlpha="0.7"/>
543 <path
544 android:pathData="M113.76,65.26C120.1,65.26 125.24,60.12 125.24,53.78C125.24,47.45 120.1,42.31 113.76,42.31C107.42,42.31 102.28,47.45 102.28,53.78C102.28,60.12 107.42,65.26 113.76,65.26Z"
545 android:strokeAlpha="0.7"
546 android:fillColor="#1C1C1C"
547 android:fillAlpha="0.7"/>
548 <path
549 android:pathData="M112.85,39.02V40.95C112.85,40.95 112.85,41.06 112.74,41.06C106.49,41.51 101.49,46.51 100.92,52.88C100.92,52.88 100.92,52.99 100.8,52.99H98.98C98.98,52.99 98.87,52.99 98.87,52.88C98.87,52.53 98.87,52.31 98.98,51.97C100.01,44.7 105.92,39.47 112.85,39.02Z"
550 android:strokeAlpha="0.7"
551 android:fillColor="#1C1C1C"
552 android:fillAlpha="0.7"/>
553 <path
554 android:pathData="M128.54,54.69C128.65,55.03 128.54,55.38 128.54,55.72C127.63,62.87 121.72,68.1 114.9,68.55C114.9,68.55 114.79,68.55 114.79,68.44V66.62C114.79,66.62 114.79,66.51 114.9,66.51C121.15,66.05 126.15,61.06 126.72,54.69C126.72,54.69 126.72,54.58 126.83,54.58H128.54V54.69Z"
555 android:strokeAlpha="0.7"
556 android:fillColor="#1C1C1C"
557 android:fillAlpha="0.7"/>
558 <path
559 android:pathData="M128.54,52.88H126.61C126.61,52.88 126.49,52.88 126.49,52.76C126.04,46.51 121.04,41.51 114.67,40.95C114.67,40.95 114.56,40.95 114.56,40.83V39.02C114.56,39.02 114.56,38.9 114.67,38.9C115.01,38.9 115.24,38.9 115.58,39.02C122.86,40.04 128.09,45.83 128.54,52.88Z"
560 android:strokeAlpha="0.7"
561 android:fillColor="#1C1C1C"
562 android:fillAlpha="0.7"/>
563 <path
564 android:pathData="M112.85,66.62V68.44C112.85,68.44 112.85,68.55 112.74,68.55C112.4,68.55 112.17,68.55 111.83,68.44C104.67,67.53 99.44,61.62 98.98,54.81C98.98,54.81 98.98,54.69 99.1,54.69H100.92C100.92,54.69 101.03,54.69 101.03,54.81C101.49,61.06 106.49,66.05 112.85,66.62C112.85,66.51 112.85,66.51 112.85,66.62Z"
565 android:strokeAlpha="0.7"
566 android:fillColor="#1C1C1C"
567 android:fillAlpha="0.7"/>
568 <path
569 android:pathData="M108.08,109.68C108.08,113.66 104.89,116.84 100.92,116.84C96.94,116.84 93.64,113.66 93.64,109.68C93.64,105.7 96.82,102.52 100.92,102.52C104.89,102.52 108.08,105.7 108.08,109.68Z"
570 android:strokeAlpha="0.7"
571 android:fillColor="#1C1C1C"
572 android:fillAlpha="0.7"/>
573 <path
574 android:pathData="M120.7,97.18C120.7,101.16 117.51,104.34 113.42,104.34C109.44,104.34 106.26,101.16 106.26,97.18C106.26,93.21 109.44,90.03 113.42,90.03C117.4,89.91 120.7,93.21 120.7,97.18Z"
575 android:strokeAlpha="0.7"
576 android:fillColor="#1C1C1C"
577 android:fillAlpha="0.7"/>
578 <path
579 android:pathData="M133.2,109.68C133.2,113.66 130.02,116.84 126.04,116.84C122.06,116.84 118.88,113.66 118.88,109.68C118.88,105.7 122.06,102.52 126.04,102.52C129.9,102.52 133.2,105.7 133.2,109.68Z"
580 android:strokeAlpha="0.7"
581 android:fillColor="#1C1C1C"
582 android:fillAlpha="0.7"/>
583 <path
584 android:pathData="M120.7,122.29C120.7,126.27 117.51,129.45 113.42,129.45C109.44,129.45 106.26,126.27 106.26,122.29C106.26,118.32 109.44,115.13 113.42,115.13C117.4,115.02 120.7,118.32 120.7,122.29Z"
585 android:strokeAlpha="0.7"
586 android:fillColor="#1C1C1C"
587 android:fillAlpha="0.7"/>
588 </group>
589 <path
590 android:pathData="M157.99,209.4C157.87,209.5 157.75,209.7 157.75,210C157.75,210.5 157.63,210.8 157.51,211.01C157.03,211.81 155.83,212.21 154.51,212.21L152.95,212.21C152.95,212.21 152.71,212.21 152.59,212.31C152.47,212.41 152.47,212.51 152.47,212.61L152.47,399.35C152.47,399.45 152.47,399.45 152.47,399.55C152.59,399.75 152.83,399.85 153.07,399.85L154.87,399.85C154.87,399.85 156.31,399.75 157.15,400.65C157.75,401.36 157.75,402.26 157.75,402.26C157.75,402.36 157.75,402.56 157.87,402.66C158.1,402.96 158.46,403.16 159.06,403.16L287.28,403.16C287.4,403.16 287.52,403.16 287.64,403.06C288,402.86 288,402.56 288,402.56L288,209.7C288,209.7 288,209.3 287.76,209.1C287.64,209 287.52,209 287.4,209L159.18,209C159.18,209 158.35,209 157.99,209.4ZM279.85,214.52C279.97,214.52 281.41,214.52 282.49,215.42C283.57,216.32 283.57,217.63 283.57,217.73L283.57,394.54C283.57,394.64 283.57,395.94 282.49,396.84C281.41,397.74 279.97,397.74 279.85,397.74L160.74,397.74C160.62,397.74 159.18,397.74 158.1,396.84C157.03,395.94 157.03,394.64 157.03,394.54L157.03,217.73C157.03,217.63 156.91,216.42 158.1,215.42C159.18,214.52 160.62,214.52 160.74,214.52L279.85,214.52Z"
591 android:strokeAlpha="0.7"
592 android:fillColor="#282828"
593 android:fillAlpha="0.7"/>
594 <path
595 android:pathData="M151.36,353.72L152.44,353.72L152.44,377.49L151.36,377.49C151.36,377.49 151,377.39 151,377.09L151,369.87C151,369.87 151,369.47 151.36,369.47L151.36,361.44C151.36,361.44 151,361.44 151,361.14L151,353.92C151.12,353.82 151.12,353.72 151.36,353.72Z"
596 android:strokeAlpha="0.7"
597 android:fillColor="#282828"
598 android:fillAlpha="0.7"/>
599 <path
600 android:pathData="M160.78,214.51L279.89,214.51C280.01,214.51 281.45,214.51 282.52,215.41C283.6,216.31 283.6,217.62 283.6,217.72L283.6,394.53C283.6,394.63 283.6,395.93 282.52,396.83C281.45,397.74 280.01,397.74 279.89,397.74L160.78,397.74C160.66,397.74 159.22,397.74 158.14,396.83C157.06,395.93 157.06,394.63 157.06,394.53L157.06,217.72C157.06,217.62 156.95,216.41 158.14,215.41C159.22,214.51 160.66,214.51 160.78,214.51Z"
601 android:strokeAlpha="0.7"
602 android:fillColor="#1C1C1C"
603 android:fillAlpha="0.7"/>
604 <group>
605 <clip-path
606 android:pathData="M368,311h64v192h-64z"/>
607 <path
608 android:pathData="M400.06,311H368V503H400.06C417.79,503 432.11,488.68 432.11,470.96V343.04C432,325.32 417.68,311 400.06,311Z"
609 android:strokeAlpha="0.7"
610 android:fillColor="#282828"
611 android:fillAlpha="0.7"/>
612 <path
613 android:strokeWidth="1"
614 android:pathData="M374.14,327.81H378.23C378.35,327.81 378.35,327.7 378.35,327.7V323.84C378.35,323.72 378.46,323.72 378.46,323.72H379.6C379.71,323.72 379.71,323.84 379.71,323.84V327.7C379.71,327.81 379.82,327.81 379.82,327.81H383.8C383.92,327.81 383.92,327.93 383.92,327.93V329.06C383.92,329.18 383.8,329.18 383.8,329.18H379.82C379.71,329.18 379.71,329.29 379.71,329.29V333.15C379.71,333.27 379.6,333.27 379.6,333.27H378.46C378.35,333.27 378.35,333.15 378.35,333.15V329.29C378.35,329.18 378.23,329.18 378.23,329.18H374.14C374.02,329.18 374.02,329.06 374.02,329.06V327.93C374.02,327.93 374.02,327.81 374.14,327.81Z"
615 android:strokeAlpha="0.7"
616 android:fillColor="#1C1C1C"
617 android:strokeColor="#1C1C1C"
618 android:fillAlpha="0.7"/>
619 <path
620 android:pathData="M399.49,423.81C405.83,423.81 410.97,418.68 410.97,412.34C410.97,406 405.83,400.86 399.49,400.86C393.15,400.86 388.01,406 388.01,412.34C388.01,418.68 393.15,423.81 399.49,423.81Z"
621 android:strokeAlpha="0.7"
622 android:fillColor="#1C1C1C"
623 android:fillAlpha="0.7"/>
624 <path
625 android:pathData="M398.58,397.68V399.5C398.58,399.5 398.58,399.61 398.46,399.61C392.21,400.07 387.21,405.07 386.64,411.43C386.64,411.43 386.64,411.54 386.53,411.54H384.71C384.71,411.54 384.6,411.54 384.6,411.43C384.6,411.09 384.6,410.86 384.71,410.52C385.73,403.25 391.64,398.02 398.58,397.68C398.58,397.57 398.58,397.57 398.58,397.68Z"
626 android:strokeAlpha="0.7"
627 android:fillColor="#1C1C1C"
628 android:fillAlpha="0.7"/>
629 <path
630 android:pathData="M414.27,413.25C414.38,413.59 414.27,413.93 414.27,414.27C413.36,421.43 407.45,426.65 400.63,427.11C400.63,427.11 400.51,427.11 400.51,426.99V425.18C400.51,425.18 400.51,425.06 400.63,425.06C406.88,424.61 411.88,419.61 412.45,413.25C412.45,413.25 412.45,413.14 412.56,413.14H414.27V413.25Z"
631 android:strokeAlpha="0.7"
632 android:fillColor="#1C1C1C"
633 android:fillAlpha="0.7"/>
634 <path
635 android:pathData="M414.27,411.43H412.33C412.33,411.43 412.22,411.43 412.22,411.32C411.77,405.07 406.76,400.07 400.4,399.5C400.4,399.5 400.28,399.5 400.28,399.39V397.57C400.28,397.57 400.28,397.46 400.4,397.46C400.74,397.46 400.97,397.46 401.31,397.57C408.58,398.59 413.81,404.39 414.27,411.43Z"
636 android:strokeAlpha="0.7"
637 android:fillColor="#1C1C1C"
638 android:fillAlpha="0.7"/>
639 <path
640 android:pathData="M398.58,425.18V426.99C398.58,426.99 398.58,427.11 398.46,427.11C398.12,427.11 397.9,427.11 397.56,426.99C390.39,426.09 385.17,420.18 384.71,413.36C384.71,413.36 384.71,413.25 384.82,413.25H386.64C386.64,413.25 386.76,413.25 386.76,413.36C387.21,419.61 392.21,424.61 398.58,425.18Z"
641 android:strokeAlpha="0.7"
642 android:fillColor="#1C1C1C"
643 android:fillAlpha="0.7"/>
644 <path
645 android:pathData="M392.67,358.15C392.67,362.12 389.48,365.3 385.51,365.3C381.53,365.42 378.23,362.12 378.23,358.15C378.23,354.17 381.41,350.99 385.51,350.99C389.48,350.99 392.67,354.17 392.67,358.15Z"
646 android:strokeAlpha="0.7"
647 android:fillColor="#1C1C1C"
648 android:fillAlpha="0.7"/>
649 <path
650 android:pathData="M405.29,345.65C405.29,349.63 402.1,352.81 398.01,352.81C394.03,352.81 390.85,349.63 390.85,345.65C390.85,341.67 394.03,338.49 398.01,338.49C401.99,338.38 405.29,341.67 405.29,345.65Z"
651 android:strokeAlpha="0.7"
652 android:fillColor="#1C1C1C"
653 android:fillAlpha="0.7"/>
654 <path
655 android:pathData="M417.79,358.15C417.79,362.12 414.61,365.3 410.63,365.3C406.65,365.3 403.47,362.12 403.47,358.15C403.47,354.17 406.65,350.99 410.63,350.99C414.49,350.99 417.79,354.17 417.79,358.15Z"
656 android:strokeAlpha="0.7"
657 android:fillColor="#1C1C1C"
658 android:fillAlpha="0.7"/>
659 <path
660 android:pathData="M405.29,370.76C405.29,374.73 402.1,377.92 398.01,377.92C394.03,377.92 390.85,374.73 390.85,370.76C390.85,366.78 394.03,363.6 398.01,363.6C401.99,363.49 405.29,366.78 405.29,370.76Z"
661 android:strokeAlpha="0.7"
662 android:fillColor="#1C1C1C"
663 android:fillAlpha="0.7"/>
664 <path
665 android:pathData="M394.15,448.81C394.15,452.33 391.3,455.17 387.78,455.17C384.26,455.17 381.41,452.33 381.41,448.81C381.41,445.29 384.26,442.45 387.78,442.45C391.3,442.56 394.15,445.4 394.15,448.81Z"
666 android:strokeAlpha="0.7"
667 android:fillColor="#1C1C1C"
668 android:fillAlpha="0.7"/>
669 </group>
670 <path
671 android:pathData="M91.95,468.95C97.99,468.95 102.9,464.05 102.9,458C102.9,451.95 97.99,447.05 91.95,447.05C85.9,447.05 81,451.95 81,458C81,464.05 85.9,468.95 91.95,468.95Z"
672 android:strokeAlpha="0.7"
673 android:fillColor="#1A1A1A"
674 android:fillAlpha="0.7"/>
675 <path
676 android:pathData="M88.14,457.83L93.88,454.5C94,454.42 94.17,454.53 94.17,454.67V461.3C94.17,461.44 94.02,461.53 93.88,461.47L88.14,458.14C88.02,458.08 88.02,457.92 88.14,457.83Z"
677 android:strokeAlpha="0.7"
678 android:fillColor="#282828"
679 android:fillAlpha="0.7"/>
680 <path
681 android:pathData="M111,449.9C117.05,449.9 121.95,444.99 121.95,438.95C121.95,432.9 117.05,428 111,428C104.95,428 100.05,432.9 100.05,438.95C100.05,444.99 104.95,449.9 111,449.9Z"
682 android:strokeAlpha="0.7"
683 android:fillColor="#1A1A1A"
684 android:fillAlpha="0.7"/>
685 <path
686 android:pathData="M111.17,435.14L114.5,440.88C114.58,440.99 114.47,441.17 114.33,441.17H107.7C107.56,441.17 107.47,441.02 107.53,440.88L110.86,435.14C110.92,435.02 111.08,435.02 111.17,435.14Z"
687 android:strokeAlpha="0.7"
688 android:fillColor="#282828"
689 android:fillAlpha="0.7"/>
690 <path
691 android:pathData="M130.05,468.95C136.1,468.95 141,464.05 141,458C141,451.95 136.1,447.05 130.05,447.05C124.01,447.05 119.1,451.95 119.1,458C119.1,464.05 124.01,468.95 130.05,468.95Z"
692 android:strokeAlpha="0.7"
693 android:fillColor="#1A1A1A"
694 android:fillAlpha="0.7"/>
695 <path
696 android:pathData="M133.86,458.17L128.12,461.5C128.01,461.58 127.83,461.47 127.83,461.33V454.7C127.83,454.56 127.98,454.47 128.12,454.53L133.86,457.86C134.01,457.92 134.01,458.08 133.86,458.17Z"
697 android:strokeAlpha="0.7"
698 android:fillColor="#282828"
699 android:fillAlpha="0.7"/>
700 <path
701 android:pathData="M111,488C117.05,488 121.95,483.1 121.95,477.05C121.95,471.01 117.05,466.1 111,466.1C104.95,466.1 100.05,471.01 100.05,477.05C100.05,483.1 104.95,488 111,488Z"
702 android:strokeAlpha="0.7"
703 android:fillColor="#1A1A1A"
704 android:fillAlpha="0.7"/>
705 <path
706 android:pathData="M110.83,480.86L107.5,475.12C107.42,475.01 107.53,474.83 107.67,474.83H114.3C114.44,474.83 114.53,474.98 114.47,475.12L111.14,480.86C111.08,481.01 110.92,481.01 110.83,480.86Z"
707 android:strokeAlpha="0.7"
708 android:fillColor="#282828"
709 android:fillAlpha="0.7"/>
710 <path
711 android:pathData="M380.95,165.95C386.99,165.95 391.9,161.05 391.9,155C391.9,148.95 386.99,144.05 380.95,144.05C374.9,144.05 370,148.95 370,155C370,161.05 374.9,165.95 380.95,165.95Z"
712 android:strokeAlpha="0.7"
713 android:fillColor="#1C1C1C"
714 android:fillAlpha="0.7"/>
715 <path
716 android:pathData="M380.46,155.54L377.68,151.3H378.96L380.98,154.54L383.05,151.3H384.27L381.49,155.54V158.7H380.49V155.54H380.46Z"
717 android:strokeAlpha="0.7"
718 android:fillColor="#282828"
719 android:fillAlpha="0.7"/>
720 <path
721 android:pathData="M399.72,185C405.76,185 410.66,180.1 410.66,174.05C410.66,168.01 405.76,163.1 399.72,163.1C393.67,163.1 388.77,168.01 388.77,174.05C388.77,180.1 393.67,185 399.72,185Z"
722 android:strokeAlpha="0.7"
723 android:fillColor="#1C1C1C"
724 android:fillAlpha="0.7"/>
725 <path
726 android:pathData="M397.44,170.64H400C400.31,170.64 400.63,170.67 400.88,170.75C401.17,170.84 401.39,170.95 401.59,171.1C401.79,171.24 401.93,171.44 402.08,171.66C402.19,171.89 402.25,172.18 402.25,172.49C402.25,172.91 402.13,173.26 401.9,173.54C401.68,173.8 401.36,173.99 400.99,174.14V174.17C401.22,174.17 401.42,174.25 401.62,174.34C401.82,174.42 401.99,174.56 402.13,174.74C402.27,174.9 402.39,175.08 402.47,175.3C402.56,175.53 402.59,175.76 402.59,176.01C402.59,176.35 402.53,176.64 402.39,176.9C402.25,177.15 402.08,177.35 401.82,177.55C401.59,177.72 401.31,177.86 400.99,177.95C400.68,178.03 400.34,178.09 399.97,178.09H397.44V170.64ZM398.44,173.71H399.8C400,173.71 400.17,173.68 400.34,173.65C400.51,173.63 400.65,173.54 400.77,173.46C400.88,173.37 400.99,173.26 401.05,173.11C401.14,172.97 401.17,172.8 401.17,172.6C401.17,172.32 401.08,172.06 400.88,171.83C400.68,171.61 400.4,171.52 400,171.52H398.44V173.71ZM398.44,177.15H399.92C400.06,177.15 400.23,177.12 400.43,177.1C400.6,177.07 400.8,177.01 400.94,176.9C401.11,176.81 401.22,176.67 401.34,176.53C401.45,176.35 401.51,176.16 401.51,175.9C401.51,175.47 401.36,175.13 401.08,174.9C400.8,174.68 400.4,174.56 399.92,174.56H398.44V177.15Z"
727 android:strokeAlpha="0.7"
728 android:fillColor="#282828"
729 android:fillAlpha="0.7"/>
730 <path
731 android:pathData="M419.05,165.95C425.1,165.95 430,161.05 430,155C430,148.95 425.1,144.05 419.05,144.05C413.01,144.05 408.1,148.95 408.1,155C408.1,161.05 413.01,165.95 419.05,165.95Z"
732 android:strokeAlpha="0.7"
733 android:fillColor="#1C1C1C"
734 android:fillAlpha="0.7"/>
735 <path
736 android:pathData="M418.63,151.3H419.54L422.69,158.67H421.53L420.79,156.85H417.29L416.55,158.67H415.38L418.63,151.3ZM420.42,155.99L419.05,152.61H419.02L417.63,155.99H420.42Z"
737 android:strokeAlpha="0.7"
738 android:fillColor="#282828"
739 android:fillAlpha="0.7"/>
740 <path
741 android:pathData="M400,146.9C406.05,146.9 410.95,141.99 410.95,135.95C410.95,129.9 406.05,125 400,125C393.95,125 389.05,129.9 389.05,135.95C389.05,141.99 393.95,146.9 400,146.9Z"
742 android:strokeAlpha="0.7"
743 android:fillColor="#1C1C1C"
744 android:fillAlpha="0.7"/>
745 <path
746 android:pathData="M399.26,135.78L396.79,132.28H398.07L400,135.12L401.9,132.28H403.16L400.68,135.78L403.41,139.67H402.1L399.97,136.46L397.84,139.67H396.59L399.26,135.78Z"
747 android:strokeAlpha="0.7"
748 android:fillColor="#282828"
749 android:fillAlpha="0.7"/>
750 </group>
751</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_info_outline.xml b/src/android/app/src/main/res/drawable/ic_info_outline.xml
new file mode 100644
index 000000000..92ae0eeaf
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_info_outline.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_install.xml b/src/android/app/src/main/res/drawable/ic_install.xml
new file mode 100644
index 000000000..01f2de3da
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_install.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportHeight="24"
5 android:viewportWidth="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M12,16.5l4,-4h-3v-9h-2v9L8,12.5l4,4zM21,3.5h-6v1.99h6v14.03L3,19.52L3,5.49h6L9,3.5L3,3.5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2v-14c0,-1.1 -0.9,-2 -2,-2z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_key.xml b/src/android/app/src/main/res/drawable/ic_key.xml
new file mode 100644
index 000000000..a3943634f
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_key.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorOnSurface"
8 android:pathData="M21,10h-8.35C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H13l2,2l2,-2l2,2l4,-4.04L21,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_launcher.xml b/src/android/app/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 000000000..3bb60fdfb
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?>
2<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3 <background android:drawable="@drawable/ic_icon_bg" />
4 <foreground android:drawable="@drawable/ic_yuzu" />
5 <monochrome android:drawable="@drawable/ic_yuzu" />
6</adaptive-icon>
diff --git a/src/android/app/src/main/res/drawable/ic_log.xml b/src/android/app/src/main/res/drawable/ic_log.xml
new file mode 100644
index 000000000..f55b9ad85
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_log.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="960"
5 android:viewportHeight="960"
6 android:tint="?attr/colorControlNormal">
7 <path
8 android:fillColor="@android:color/white"
9 android:pathData="M360,720L600,720Q617,720 628.5,708.5Q640,697 640,680Q640,663 628.5,651.5Q617,640 600,640L360,640Q343,640 331.5,651.5Q320,663 320,680Q320,697 331.5,708.5Q343,720 360,720ZM360,560L600,560Q617,560 628.5,548.5Q640,537 640,520Q640,503 628.5,491.5Q617,480 600,480L360,480Q343,480 331.5,491.5Q320,503 320,520Q320,537 331.5,548.5Q343,560 360,560ZM240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L527,80Q543,80 557.5,86Q572,92 583,103L777,297Q788,308 794,322.5Q800,337 800,353L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,320L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L560,360Q543,360 531.5,348.5Q520,337 520,320ZM240,160L240,160L240,320Q240,337 240,348.5Q240,360 240,360L240,360L240,160L240,320Q240,337 240,348.5Q240,360 240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z"/>
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_nfc.xml b/src/android/app/src/main/res/drawable/ic_nfc.xml
new file mode 100644
index 000000000..3dacf798b
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_nfc.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,20L4,20L4,4h16v16zM18,6h-5c-1.1,0 -2,0.9 -2,2v2.28c-0.6,0.35 -1,0.98 -1,1.72 0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-0.74 -0.4,-1.38 -1,-1.72L13,8h3v8L8,16L8,8h2L10,6L6,6v12h12L18,6z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_notification.xml b/src/android/app/src/main/res/drawable/ic_notification.xml
new file mode 100644
index 000000000..b413f7585
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_notification.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorOnSurface"
8 android:pathData="M7.58,4.08L6.15,2.65C3.75,4.48 2.17,7.3 2.03,10.5h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10.5h2c-0.15,-3.2 -1.73,-6.02 -4.12,-7.85l-1.42,1.43c2.02,1.45 3.39,3.77 3.54,6.42zM18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2v-5zM12,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.14 1.18,-0.58 1.44,-1.18 0.1,-0.24 0.15,-0.5 0.15,-0.78h-4c0.01,1.1 0.9,2 2.01,2z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_options.xml b/src/android/app/src/main/res/drawable/ic_options.xml
new file mode 100644
index 000000000..91d52f1b8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_options.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_palette.xml b/src/android/app/src/main/res/drawable/ic_palette.xml
new file mode 100644
index 000000000..43daec1ff
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_palette.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10c1.38,0 2.5,-1.12 2.5,-2.5c0,-0.61 -0.23,-1.2 -0.64,-1.67c-0.08,-0.1 -0.13,-0.21 -0.13,-0.33c0,-0.28 0.22,-0.5 0.5,-0.5H16c3.31,0 6,-2.69 6,-6C22,6.04 17.51,2 12,2zM17.5,13c-0.83,0 -1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5C19,12.33 18.33,13 17.5,13zM14.5,9C13.67,9 13,8.33 13,7.5C13,6.67 13.67,6 14.5,6S16,6.67 16,7.5C16,8.33 15.33,9 14.5,9zM5,11.5C5,10.67 5.67,10 6.5,10S8,10.67 8,11.5C8,12.33 7.33,13 6.5,13S5,12.33 5,11.5zM11,7.5C11,8.33 10.33,9 9.5,9S8,8.33 8,7.5C8,6.67 8.67,6 9.5,6S11,6.67 11,7.5z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_pause.xml b/src/android/app/src/main/res/drawable/ic_pause.xml
new file mode 100644
index 000000000..adb3ababc
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_pause.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportHeight="24"
5 android:viewportWidth="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_play.xml b/src/android/app/src/main/res/drawable/ic_play.xml
new file mode 100644
index 000000000..7f01dc599
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_play.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportHeight="24"
5 android:viewportWidth="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M8,5v14l11,-7z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_save.xml b/src/android/app/src/main/res/drawable/ic_save.xml
new file mode 100644
index 000000000..a9af3d9cf
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_save.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="960"
5 android:viewportHeight="960"
6 android:tint="?attr/colorControlNormal">
7 <path
8 android:fillColor="@android:color/white"
9 android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L647,120Q663,120 677.5,126Q692,132 703,143L817,257Q828,268 834,282.5Q840,297 840,313L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM760,314L646,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,314ZM480,720Q530,720 565,685Q600,650 600,600Q600,550 565,515Q530,480 480,480Q430,480 395,515Q360,550 360,600Q360,650 395,685Q430,720 480,720ZM280,400L560,400Q577,400 588.5,388.5Q600,377 600,360L600,280Q600,263 588.5,251.5Q577,240 560,240L280,240Q263,240 251.5,251.5Q240,263 240,280L240,360Q240,377 251.5,388.5Q263,400 280,400ZM200,314L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200L200,200L200,314Z"/>
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_search.xml b/src/android/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 000000000..bb0726851
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_settings.xml b/src/android/app/src/main/res/drawable/ic_settings.xml
new file mode 100644
index 000000000..e527f85fc
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_settings.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportHeight="24"
5 android:viewportWidth="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_settings_outline.xml b/src/android/app/src/main/res/drawable/ic_settings_outline.xml
new file mode 100644
index 000000000..13b2745bf
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_settings_outline.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98 0,-0.34 -0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.09,-0.16 -0.26,-0.25 -0.44,-0.25 -0.06,0 -0.12,0.01 -0.17,0.03l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.06,-0.02 -0.12,-0.03 -0.18,-0.03 -0.17,0 -0.34,0.09 -0.43,0.25l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98 0,0.33 0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.09,0.16 0.26,0.25 0.44,0.25 0.06,0 0.12,-0.01 0.17,-0.03l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.06,0.02 0.12,0.03 0.18,0.03 0.17,0 0.34,-0.09 0.43,-0.25l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM17.45,11.27c0.04,0.31 0.05,0.52 0.05,0.73 0,0.21 -0.02,0.43 -0.05,0.73l-0.14,1.13 0.89,0.7 1.08,0.84 -0.7,1.21 -1.27,-0.51 -1.04,-0.42 -0.9,0.68c-0.43,0.32 -0.84,0.56 -1.25,0.73l-1.06,0.43 -0.16,1.13 -0.2,1.35h-1.4l-0.19,-1.35 -0.16,-1.13 -1.06,-0.43c-0.43,-0.18 -0.83,-0.41 -1.23,-0.71l-0.91,-0.7 -1.06,0.43 -1.27,0.51 -0.7,-1.21 1.08,-0.84 0.89,-0.7 -0.14,-1.13c-0.03,-0.31 -0.05,-0.54 -0.05,-0.74s0.02,-0.43 0.05,-0.73l0.14,-1.13 -0.89,-0.7 -1.08,-0.84 0.7,-1.21 1.27,0.51 1.04,0.42 0.9,-0.68c0.43,-0.32 0.84,-0.56 1.25,-0.73l1.06,-0.43 0.16,-1.13 0.2,-1.35h1.39l0.19,1.35 0.16,1.13 1.06,0.43c0.43,0.18 0.83,0.41 1.23,0.71l0.91,0.7 1.06,-0.43 1.27,-0.51 0.7,1.21 -1.07,0.85 -0.89,0.7 0.14,1.13zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_system_update_alt.xml b/src/android/app/src/main/res/drawable/ic_system_update_alt.xml
new file mode 100644
index 000000000..0f6adfdb8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_system_update_alt.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="48dp"
3 android:height="48dp"
4 android:viewportWidth="960"
5 android:viewportHeight="960">
6 <path
7 android:fillColor="#FF000000"
8 android:pathData="M140,800q-24,0 -42,-18t-18,-42v-520q0,-24 18,-42t42,-18h250v60L140,220v520h680v-520L570,220v-60h250q24,0 42,18t18,42v520q0,24 -18,42t-42,18L140,800ZM480,615L280,415l43,-43 127,127v-339h60v339l127,-127 43,43 -200,200Z"/>
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_unlock.xml b/src/android/app/src/main/res/drawable/ic_unlock.xml
new file mode 100644
index 000000000..40952cbc5
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_unlock.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_website.xml b/src/android/app/src/main/res/drawable/ic_website.xml
new file mode 100644
index 000000000..f35b84a7c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_website.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_yuzu.xml b/src/android/app/src/main/res/drawable/ic_yuzu.xml
new file mode 100644
index 000000000..5e2a8efd0
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_yuzu.xml
@@ -0,0 +1,22 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="200dp"
3 android:height="200dp"
4 android:viewportWidth="500"
5 android:viewportHeight="500">
6 <path
7 android:fillColor="#FF3C28"
8 android:fillType="nonZero"
9 android:pathData="M262.66,175.11L262.66,375.05C318.54,375.05 363.85,330.29 363.85,275.08C363.85,219.87 318.54,175.11 262.66,175.11M282.43,197.01C318.67,206 344.09,238.19 344.09,275.11C344.09,312.03 318.67,344.22 282.43,353.2L282.43,197.01"
10 android:strokeWidth="1.46"
11 android:strokeColor="#00000000"
12 android:strokeLineCap="butt"
13 android:strokeLineJoin="miter" />
14 <path
15 android:fillColor="#0AB9E6"
16 android:fillType="nonZero"
17 android:pathData="M237.31,125.11C181.43,125.11 136.12,169.87 136.12,225.08C136.12,280.29 181.43,325.05 237.31,325.05ZM217.57,147.01L217.57,303.2C189.11,296.16 166.67,274.54 158.84,246.6C151.01,218.65 159,188.71 179.75,168.21C190.16,157.86 203.24,150.53 217.57,147.01"
18 android:strokeWidth="1.46"
19 android:strokeColor="#00000000"
20 android:strokeLineCap="butt"
21 android:strokeLineJoin="miter" />
22</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_yuzu_full.xml b/src/android/app/src/main/res/drawable/ic_yuzu_full.xml
new file mode 100644
index 000000000..04e458400
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_yuzu_full.xml
@@ -0,0 +1,12 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="155.3dp"
3 android:height="172.55dp"
4 android:viewportWidth="155.3"
5 android:viewportHeight="172.55">
6 <path
7 android:fillColor="#FF3C28"
8 android:pathData="M86.28,34.51v138a69,69 0,0 0,0 -138M99.76,49.63a55.57,55.57 0,0 1,0 107.8V49.63" />
9 <path
10 android:fillColor="#0AB9E6"
11 android:pathData="M69,0a69,69 0,0 0,0 138ZM55.54,15.12v107.8A55.55,55.55 0,0 1,29.75 29.75,55.1 55.1,0 0,1 55.54,15.12" />
12</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_yuzu_title.xml b/src/android/app/src/main/res/drawable/ic_yuzu_title.xml
new file mode 100644
index 000000000..b733e5248
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_yuzu_title.xml
@@ -0,0 +1,24 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="340.97dp"
3 android:height="389.85dp"
4 android:viewportWidth="340.97"
5 android:viewportHeight="389.85">
6 <path
7 android:fillColor="?attr/colorOnSurface"
8 android:pathData="M341,268.68v73c0,14.5 -2.24,25.24 -6.83,32.82 -5.92,10.15 -16.21,15.32 -30.54,15.32S279,384.61 273,374.27c-4.56,-7.64 -6.8,-18.42 -6.8,-32.92V268.68a4.52,4.52 0,0 1,4.51 -4.51H273a4.5,4.5 0,0 1,4.5 4.51v72.5c0,33.53 14.88,37.4 26.07,37.4 12.14,0 26.08,-4.17 26.08,-36.71V268.68a4.52,4.52 0,0 1,4.52 -4.51h2.27A4.5,4.5 0,0 1,341 268.68Z" />
9 <path
10 android:fillColor="?attr/colorOnSurface"
11 android:pathData="M246.49,389.85H178.6c-2.35,0 -4.72,-1.88 -4.72,-6.08a8.28,8.28 0,0 1,1.33 -4.48l60.33,-104.47H186a4.51,4.51 0,0 1,-4.51 -4.51v-1.58a4.51,4.51 0,0 1,4.48 -4.51h0.8c58.69,-0.11 59.12,0 59.67,0.07a5.19,5.19 0,0 1,4 5.8,8.69 8.69,0 0,1 -1.33,3.76l-60.6,104.77h58a4.51,4.51 0,0 1,4.51 4.51v2.21A4.51,4.51 0,0 1,246.49 389.85Z" />
12 <path
13 android:fillColor="?attr/colorOnSurface"
14 android:pathData="M73.6,268.68v82.06c0,26 -11.8,38.44 -37.12,39.09h-0.12a4.51,4.51 0,0 1,-4.51 -4.51V383a4.51,4.51 0,0 1,4.48 -4.5c18.49,-0.15 26,-8.23 26,-27.9v-2.37A32.34,32.34 0,0 1,59 351.46c-6.39,5.5 -14.5,8.29 -24.07,8.29C12.09,359.75 0,347.34 0,323.86V268.68a4.52,4.52 0,0 1,4.51 -4.51H6.73a4.52,4.52 0,0 1,4.5 4.51v55c0,7.6 1.82,14.22 5,18.18 3.57,4.56 9.17,6.49 18.75,6.49 10.13,0 17.32,-3.76 22,-11.5 3.61,-5.92 5.43,-13.66 5.43,-23V268.68a4.52,4.52 0,0 1,4.51 -4.51h2.22A4.52,4.52 0,0 1,73.6 268.68Z" />
15 <path
16 android:fillColor="?attr/colorOnSurface"
17 android:pathData="M163.27,268.68v73c0,14.5 -2.24,25.24 -6.84,32.82 -5.92,10.15 -16.2,15.32 -30.53,15.32s-24.62,-5.23 -30.58,-15.57c-4.56,-7.64 -6.79,-18.42 -6.79,-32.92V268.68A4.51,4.51 0,0 1,93 264.17h2.28a4.51,4.51 0,0 1,4.51 4.51v72.5c0,33.53 14.88,37.4 26.07,37.4 12.14,0 26.08,-4.17 26.08,-36.71V268.68a4.51,4.51 0,0 1,4.51 -4.51h2.27A4.51,4.51 0,0 1,163.27 268.68Z" />
18 <path
19 android:fillColor="#ff3c28"
20 android:pathData="M181.2,42.83V214.17a85.67,85.67 0,0 0,0 -171.34M197.93,61.6a69,69 0,0 1,0 133.8V61.6" />
21 <path
22 android:fillColor="#0ab9e6"
23 android:pathData="M159.78,0a85.67,85.67 0,1 0,0 171.33ZM143.05,18.77v133.8A69,69 0,0 1,111 36.92a68.47,68.47 0,0 1,32 -18.15" />
24</vector>
diff --git a/src/android/app/src/main/res/drawable/joystick.xml b/src/android/app/src/main/res/drawable/joystick.xml
new file mode 100644
index 000000000..bdd071212
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/joystick.xml
@@ -0,0 +1,45 @@
1<vector android:alpha="0.6" android:height="161.61dp"
2 android:viewportHeight="161.61" android:viewportWidth="161.61"
3 android:width="161.61dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.8"
6 android:pathData="M91.23,0.68a80.8,80.8 0,1 0,69.69 90.55A80.81,80.81 0,0 0,91.23 0.68ZM80.8,150.68A69.84,69.84 0,1 1,150.64 80.8,69.92 69.92,0 0,1 80.8,150.64Z" android:strokeAlpha="0.8">
7 <aapt:attr name="android:fillColor">
8 <gradient android:centerX="68.25" android:centerY="57.75"
9 android:gradientRadius="122.17" android:type="radial">
10 <item android:color="#FFD9D9D9" android:offset="0.44"/>
11 <item android:color="#FF141414" android:offset="1"/>
12 </gradient>
13 </aapt:attr>
14 </path>
15 <path android:fillAlpha="0.8"
16 android:pathData="M80.8,80.8m-67.05,0a67.05,67.05 0,1 1,134.1 0a67.05,67.05 0,1 1,-134.1 0" android:strokeAlpha="0.8">
17 <aapt:attr name="android:fillColor">
18 <gradient android:centerX="80.49" android:centerY="60.19"
19 android:gradientRadius="88.23" android:type="radial">
20 <item android:color="#FFBABABA" android:offset="0.15"/>
21 <item android:color="#FF9E9E9E" android:offset="0.46"/>
22 <item android:color="#FF868686" android:offset="0.63"/>
23 <item android:color="#FF575757" android:offset="1"/>
24 </gradient>
25 </aapt:attr>
26 </path>
27 <path android:fillAlpha="0.8"
28 android:pathData="M80.8,150.64A69.84,69.84 0,1 1,150.64 80.8,69.92 69.92,0 0,1 80.8,150.64ZM80.8,13.76a67,67 0,1 0,67.05 67A67.11,67.11 0,0 0,80.8 13.76Z" android:strokeAlpha="0.8">
29 <aapt:attr name="android:fillColor">
30 <gradient android:centerX="80.8" android:centerY="80.8"
31 android:gradientRadius="97.63" android:type="radial">
32 <item android:color="#FFC2C2C3" android:offset="0.04"/>
33 <item android:color="#FFC0C0C1" android:offset="0.35"/>
34 <item android:color="#FFB9B9BA" android:offset="0.47"/>
35 <item android:color="#FFADADAE" android:offset="0.56"/>
36 <item android:color="#FF9C9C9D" android:offset="0.63"/>
37 <item android:color="#FF868687" android:offset="0.69"/>
38 <item android:color="#FF6A6A6B" android:offset="0.74"/>
39 <item android:color="#FF4A4A4A" android:offset="0.79"/>
40 <item android:color="#FF252525" android:offset="0.83"/>
41 <item android:color="#FF000000" android:offset="0.87"/>
42 </gradient>
43 </aapt:attr>
44 </path>
45</vector>
diff --git a/src/android/app/src/main/res/drawable/joystick_depressed.xml b/src/android/app/src/main/res/drawable/joystick_depressed.xml
new file mode 100644
index 000000000..ad51d73ce
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/joystick_depressed.xml
@@ -0,0 +1,10 @@
1<vector android:alpha="0.6" android:height="161.73dp"
2 android:viewportHeight="161.73" android:viewportWidth="161.73"
3 android:width="161.73dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#FF000000"
5 android:pathData="M91.3,0.68A80.86,80.86 0,1 0,161.05 91.3,80.87 80.87,0 0,0 91.3,0.68ZM80.87,150.76a69.9,69.9 0,1 1,69.89 -69.89A70,70 0,0 1,80.87 150.76Z" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.5" android:fillColor="#FF000000"
7 android:pathData="M80.87,80.87m-67.1,0a67.1,67.1 0,1 1,134.2 0a67.1,67.1 0,1 1,-134.2 0" android:strokeAlpha="0.5"/>
8 <path android:fillAlpha="0.75" android:fillColor="#fff"
9 android:pathData="M80.87,150.76a69.9,69.9 0,1 1,69.89 -69.89A70,70 0,0 1,80.87 150.76ZM80.87,13.76A67.1,67.1 0,1 0,148 80.87,67.17 67.17,0 0,0 80.87,13.77Z" android:strokeAlpha="0.75"/>
10</vector>
diff --git a/src/android/app/src/main/res/drawable/joystick_range.xml b/src/android/app/src/main/res/drawable/joystick_range.xml
new file mode 100644
index 000000000..f6282b5c8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/joystick_range.xml
@@ -0,0 +1,38 @@
1<vector android:alpha="0.6" android:height="265.64dp"
2 android:viewportHeight="265.64" android:viewportWidth="265.64"
3 android:width="265.64dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.8"
6 android:pathData="M132.82,132.82m-113.12,0a113.12,113.12 0,1 1,226.24 0a113.12,113.12 0,1 1,-226.24 0" android:strokeAlpha="0.8">
7 <aapt:attr name="android:fillColor">
8 <gradient android:centerX="132.82" android:centerY="132.82"
9 android:gradientRadius="195.71" android:type="radial">
10 <item android:color="#00000000" android:offset="0"/>
11 <item android:color="#14161515" android:offset="0.27"/>
12 <item android:color="#30333031" android:offset="0.42"/>
13 <item android:color="#42393738" android:offset="0.45"/>
14 <item android:color="#754B494A" android:offset="0.51"/>
15 <item android:color="#C6676666" android:offset="0.59"/>
16 <item android:color="#FF7A7A7A" android:offset="0.63"/>
17 <item android:color="#FF787878" android:offset="0.99"/>
18 <item android:color="#FF787878" android:offset="0.99"/>
19 </gradient>
20 </aapt:attr>
21 </path>
22 <path android:fillAlpha="0.6"
23 android:pathData="m18.72,64.82a132.8,132.8 0,1 0,182.06 -46.1,132.8 132.8,0 0,0 -182.06,46.1zM229.98,190.7a113.12,113.12 0,1 1,-39.28 -155.08,113.12 113.12,0 0,1 39.28,155.08z" android:strokeAlpha="0.6">
24 <aapt:attr name="android:fillColor">
25 <gradient android:centerX="132.82" android:centerY="132.7"
26 android:gradientRadius="141.24" android:type="radial">
27 <item android:color="#FF969696" android:offset="0"/>
28 <item android:color="#FF949494" android:offset="0.8"/>
29 <item android:color="#FF8D8D8D" android:offset="0.84"/>
30 <item android:color="#FF828282" android:offset="0.87"/>
31 <item android:color="#FF717171" android:offset="0.9"/>
32 <item android:color="#FF5B5B5B" android:offset="0.94"/>
33 <item android:color="#FF404040" android:offset="0.98"/>
34 <item android:color="#FF303030" android:offset="1"/>
35 </gradient>
36 </aapt:attr>
37 </path>
38</vector>
diff --git a/src/android/app/src/main/res/drawable/l_shoulder.xml b/src/android/app/src/main/res/drawable/l_shoulder.xml
new file mode 100644
index 000000000..28f9a9950
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/l_shoulder.xml
@@ -0,0 +1,23 @@
1<vector android:alpha="0.6" android:height="98.58dp"
2 android:viewportHeight="98.58" android:viewportWidth="244.91"
3 android:width="244.91dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.6"
6 android:pathData="M33.05,0L211.86,0A33.05,33.05 0,0 1,244.91 33.05L244.91,65.53A33.05,33.05 0,0 1,211.86 98.58L33.05,98.58A33.05,33.05 0,0 1,0 65.53L0,33.05A33.05,33.05 0,0 1,33.05 0z" android:strokeAlpha="0.6">
7 <aapt:attr name="android:fillColor">
8 <gradient android:endX="244.91" android:endY="49.29"
9 android:startX="0" android:startY="49.29" android:type="linear">
10 <item android:color="#FFC3C4C5" android:offset="0"/>
11 <item android:color="#FFC4C5C5" android:offset="0.05"/>
12 <item android:color="#FFC7C7C7" android:offset="0.47"/>
13 <item android:color="#F7C4C4C4" android:offset="0.54"/>
14 <item android:color="#E5BABABA" android:offset="0.65"/>
15 <item android:color="#C6ABABAB" android:offset="0.77"/>
16 <item android:color="#9E969696" android:offset="0.91"/>
17 <item android:color="#7F878787" android:offset="1"/>
18 </gradient>
19 </aapt:attr>
20 </path>
21 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
22 android:pathData="M106.15,20h7.57V72.24h25v6.35h-32.6Z" android:strokeAlpha="0.75"/>
23</vector>
diff --git a/src/android/app/src/main/res/drawable/l_shoulder_depressed.xml b/src/android/app/src/main/res/drawable/l_shoulder_depressed.xml
new file mode 100644
index 000000000..2f9a1fd7e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/l_shoulder_depressed.xml
@@ -0,0 +1,8 @@
1<vector android:alpha="0.6" android:height="98.58dp"
2 android:viewportHeight="98.58" android:viewportWidth="244.91"
3 android:width="244.91dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M33.05,0L211.86,0A33.05,33.05 0,0 1,244.91 33.05L244.91,65.53A33.05,33.05 0,0 1,211.86 98.58L33.05,98.58A33.05,33.05 0,0 1,0 65.53L0,33.05A33.05,33.05 0,0 1,33.05 0z" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M106.15,20h7.57V72.24h25v6.35h-32.6Z" android:strokeAlpha="0.75"/>
8</vector>
diff --git a/src/android/app/src/main/res/drawable/premium_background.xml b/src/android/app/src/main/res/drawable/premium_background.xml
new file mode 100644
index 000000000..c9c41ddbe
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/premium_background.xml
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="utf-8"?>
2<shape xmlns:android="http://schemas.android.com/apk/res/android">
3 <gradient
4 android:type="linear"
5 android:angle="45"
6 android:startColor="@color/yuzu_ea_background_start"
7 android:endColor="@color/yuzu_ea_background_end" />
8 <corners android:radius="12dp" />
9</shape>
diff --git a/src/android/app/src/main/res/drawable/r_shoulder.xml b/src/android/app/src/main/res/drawable/r_shoulder.xml
new file mode 100644
index 000000000..97731cad2
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/r_shoulder.xml
@@ -0,0 +1,23 @@
1<vector android:alpha="0.6" android:height="98.58dp"
2 android:viewportHeight="98.58" android:viewportWidth="244.91"
3 android:width="244.91dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.6"
6 android:pathData="M211.86,98.58L33.05,98.58A33.05,33.05 0,0 1,0 65.53L0,33.05A33.05,33.05 0,0 1,33.05 0L211.86,0A33.05,33.05 0,0 1,244.91 33.05L244.91,65.53A33.05,33.05 0,0 1,211.86 98.58z" android:strokeAlpha="0.6">
7 <aapt:attr name="android:fillColor">
8 <gradient android:endX="0" android:endY="49.29"
9 android:startX="244.91" android:startY="49.29" android:type="linear">
10 <item android:color="#FFC3C4C5" android:offset="0"/>
11 <item android:color="#FFC4C5C5" android:offset="0.05"/>
12 <item android:color="#FFC7C7C7" android:offset="0.47"/>
13 <item android:color="#F7C4C4C4" android:offset="0.54"/>
14 <item android:color="#E5BABABA" android:offset="0.65"/>
15 <item android:color="#C6ABABAB" android:offset="0.77"/>
16 <item android:color="#9E969696" android:offset="0.91"/>
17 <item android:color="#7F878787" android:offset="1"/>
18 </gradient>
19 </aapt:attr>
20 </path>
21 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
22 android:pathData="M103.37,21a78.13,78.13 0,0 1,14.52 -1.22c8.08,0 13.3,1.48 17,4.78a14.59,14.59 0,0 1,4.61 11.13c0,7.73 -4.87,12.86 -11,15v0.26c4.52,1.56 7.21,5.74 8.6,11.82 1.92,8.17 3.31,13.82 4.52,16.08h-7.82c-1,-1.65 -2.26,-6.69 -3.91,-14 -1.74,-8.09 -4.87,-11.13 -11.74,-11.39h-7.12L111.03,78.8h-7.57ZM110.94,47.68h7.73c8.09,0 13.22,-4.43 13.22,-11.12 0,-7.57 -5.48,-10.87 -13.48,-11a30.82,30.82 0,0 0,-7.47 0.7Z" android:strokeAlpha="0.75"/>
23</vector>
diff --git a/src/android/app/src/main/res/drawable/r_shoulder_depressed.xml b/src/android/app/src/main/res/drawable/r_shoulder_depressed.xml
new file mode 100644
index 000000000..e3aa46aa1
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/r_shoulder_depressed.xml
@@ -0,0 +1,8 @@
1<vector android:alpha="0.6" android:height="98.58dp"
2 android:viewportHeight="98.58" android:viewportWidth="244.91"
3 android:width="244.91dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M211.86,98.58L33.05,98.58A33.05,33.05 0,0 1,0 65.53L0,33.05A33.05,33.05 0,0 1,33.05 0L211.86,0A33.05,33.05 0,0 1,244.91 33.05L244.91,65.53A33.05,33.05 0,0 1,211.86 98.58z" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M103.37,21a78.13,78.13 0,0 1,14.52 -1.22c8.08,0 13.3,1.48 17,4.78a14.59,14.59 0,0 1,4.61 11.13c0,7.73 -4.87,12.86 -11,15v0.26c4.52,1.56 7.21,5.74 8.6,11.82 1.92,8.17 3.31,13.82 4.52,16.08h-7.82c-1,-1.65 -2.26,-6.69 -3.91,-14 -1.74,-8.09 -4.87,-11.13 -11.74,-11.39h-7.12L111.03,78.8h-7.57ZM110.94,47.68h7.73c8.09,0 13.22,-4.43 13.22,-11.12 0,-7.57 -5.48,-10.87 -13.48,-11a30.82,30.82 0,0 0,-7.47 0.7Z" android:strokeAlpha="0.75"/>
8</vector>
diff --git a/src/android/app/src/main/res/drawable/selector_cartridge.xml b/src/android/app/src/main/res/drawable/selector_cartridge.xml
new file mode 100644
index 000000000..85c918dae
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/selector_cartridge.xml
@@ -0,0 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?>
2<selector xmlns:android="http://schemas.android.com/apk/res/android">
3 <item android:drawable="@drawable/ic_cartridge_outline" android:state_checked="false"/>
4 <item android:drawable="@drawable/ic_cartridge" android:state_checked="true"/>
5</selector>
diff --git a/src/android/app/src/main/res/drawable/selector_settings.xml b/src/android/app/src/main/res/drawable/selector_settings.xml
new file mode 100644
index 000000000..23748feb0
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/selector_settings.xml
@@ -0,0 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?>
2<selector xmlns:android="http://schemas.android.com/apk/res/android">
3 <item android:drawable="@drawable/ic_settings_outline" android:state_checked="false"/>
4 <item android:drawable="@drawable/ic_settings" android:state_checked="true"/>
5</selector>
diff --git a/src/android/app/src/main/res/drawable/zl_trigger.xml b/src/android/app/src/main/res/drawable/zl_trigger.xml
new file mode 100644
index 000000000..436461c3b
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/zl_trigger.xml
@@ -0,0 +1,25 @@
1<vector android:alpha="0.6" android:height="98.58dp"
2 android:viewportHeight="98.58" android:viewportWidth="244.91"
3 android:width="244.91dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.6"
6 android:pathData="M84.62,0h145a15.32,15.32 0,0 1,15.32 15.32V67a31.54,31.54 0,0 1,-31.54 31.54H14a14,14 0,0 1,-14 -14v0A84.62,84.62 0,0 1,84.62 0Z" android:strokeAlpha="0.6">
7 <aapt:attr name="android:fillColor">
8 <gradient android:endX="244.91" android:endY="49.29"
9 android:startX="0" android:startY="49.29" android:type="linear">
10 <item android:color="#FFC3C4C5" android:offset="0"/>
11 <item android:color="#FFC4C5C5" android:offset="0.05"/>
12 <item android:color="#FFC7C7C7" android:offset="0.47"/>
13 <item android:color="#F7C4C4C4" android:offset="0.54"/>
14 <item android:color="#E5BABABA" android:offset="0.65"/>
15 <item android:color="#C6ABABAB" android:offset="0.77"/>
16 <item android:color="#9E969696" android:offset="0.91"/>
17 <item android:color="#7F878787" android:offset="1"/>
18 </gradient>
19 </aapt:attr>
20 </path>
21 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
22 android:pathData="M80.12,74.15 L112.63,26.6v-0.26H82.9V20h39.56v4.6L90.12,72v0.26h32.77v6.35H80.12Z" android:strokeAlpha="0.75"/>
23 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
24 android:pathData="M132.19,20h7.56V72.24h25v6.35h-32.6Z" android:strokeAlpha="0.75"/>
25</vector>
diff --git a/src/android/app/src/main/res/drawable/zl_trigger_depressed.xml b/src/android/app/src/main/res/drawable/zl_trigger_depressed.xml
new file mode 100644
index 000000000..00393c04d
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/zl_trigger_depressed.xml
@@ -0,0 +1,10 @@
1<vector android:alpha="0.6" android:height="98.58dp"
2 android:viewportHeight="98.58" android:viewportWidth="244.91"
3 android:width="244.91dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M84.62,0h145a15.32,15.32 0,0 1,15.32 15.32V67a31.54,31.54 0,0 1,-31.54 31.54H14a14,14 0,0 1,-14 -14v0A84.62,84.62 0,0 1,84.62 0Z" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M80.12,74.15 L112.63,26.6v-0.26H82.9V20h39.56v4.6L90.12,72v0.26h32.77v6.35H80.12Z" android:strokeAlpha="0.75"/>
8 <path android:fillAlpha="0.75" android:fillColor="#fff"
9 android:pathData="M132.19,20h7.56V72.24h25v6.35h-32.6Z" android:strokeAlpha="0.75"/>
10</vector>
diff --git a/src/android/app/src/main/res/drawable/zr_trigger.xml b/src/android/app/src/main/res/drawable/zr_trigger.xml
new file mode 100644
index 000000000..2b3a92184
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/zr_trigger.xml
@@ -0,0 +1,25 @@
1<vector android:alpha="0.6" android:height="98.58dp"
2 android:viewportHeight="98.58" android:viewportWidth="244.91"
3 android:width="244.91dp"
4 xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
5 <path android:fillAlpha="0.6"
6 android:pathData="M230.91,98.58l-199.4,-0a31.54,31.54 135,0 1,-31.54 -31.54L-0.03,15.31a15.32,15.32 0,0 1,15.32 -15.32l145,-0A84.62,84.62 0,0 1,244.91 84.58l-0,-0A14,14 0,0 1,230.91 98.58Z" android:strokeAlpha="0.6">
7 <aapt:attr name="android:fillColor">
8 <gradient android:endX="0" android:endY="49.29"
9 android:startX="244.91" android:startY="49.29" android:type="linear">
10 <item android:color="#FFC3C4C5" android:offset="0"/>
11 <item android:color="#FFC4C5C5" android:offset="0.05"/>
12 <item android:color="#FFC7C7C7" android:offset="0.47"/>
13 <item android:color="#F7C4C4C4" android:offset="0.54"/>
14 <item android:color="#E5BABABA" android:offset="0.65"/>
15 <item android:color="#C6ABABAB" android:offset="0.77"/>
16 <item android:color="#9E969696" android:offset="0.91"/>
17 <item android:color="#7F878787" android:offset="1"/>
18 </gradient>
19 </aapt:attr>
20 </path>
21 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
22 android:pathData="M77.34,74.37l32.51,-47.55v-0.26H80.12V20.21h39.55v4.61L87.34,72.2v0.26h32.77V78.8H77.34Z" android:strokeAlpha="0.75"/>
23 <path android:fillAlpha="0.75" android:fillColor="#FF000000"
24 android:pathData="M129.41,21a78,78 0,0 1,14.51 -1.22c8.09,0 13.3,1.48 17,4.78a14.62,14.62 0,0 1,4.6 11.13c0,7.73 -4.87,12.86 -11,15v0.26c4.52,1.56 7.22,5.74 8.61,11.82 1.91,8.17 3.3,13.82 4.52,16.08h-7.82c-1,-1.65 -2.26,-6.69 -3.92,-14C154.1,56.72 151,53.68 144.1,53.42H137V78.8h-7.56ZM137,47.68h7.74c8.08,0 13.21,-4.43 13.21,-11.12 0,-7.57 -5.48,-10.87 -13.47,-11a30.92,30.92 0,0 0,-7.48 0.7Z" android:strokeAlpha="0.75"/>
25</vector>
diff --git a/src/android/app/src/main/res/drawable/zr_trigger_depressed.xml b/src/android/app/src/main/res/drawable/zr_trigger_depressed.xml
new file mode 100644
index 000000000..8a9ee5036
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/zr_trigger_depressed.xml
@@ -0,0 +1,10 @@
1<vector android:alpha="0.6" android:height="98.58dp"
2 android:viewportHeight="98.58" android:viewportWidth="244.91"
3 android:width="244.91dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 <path android:fillAlpha="0.5" android:fillColor="#151515"
5 android:pathData="M230.91,98.58l-199.4,-0a31.54,31.54 135,0 1,-31.54 -31.54L-0.03,15.31a15.32,15.32 0,0 1,15.32 -15.32l145,-0A84.62,84.62 0,0 1,244.91 84.58l-0,-0A14,14 0,0 1,230.91 98.58Z" android:strokeAlpha="0.5"/>
6 <path android:fillAlpha="0.75" android:fillColor="#fff"
7 android:pathData="M77.34,74.37l32.51,-47.55v-0.26H80.12V20.21h39.55v4.61L87.34,72.2v0.26h32.77V78.8H77.34Z" android:strokeAlpha="0.75"/>
8 <path android:fillAlpha="0.75" android:fillColor="#fff"
9 android:pathData="M129.41,21a78,78 0,0 1,14.51 -1.22c8.09,0 13.3,1.48 17,4.78a14.62,14.62 0,0 1,4.6 11.13c0,7.73 -4.87,12.86 -11,15v0.26c4.52,1.56 7.22,5.74 8.61,11.82 1.91,8.17 3.3,13.82 4.52,16.08h-7.82c-1,-1.65 -2.26,-6.69 -3.92,-14C154.1,56.72 151,53.68 144.1,53.42H137V78.8h-7.56ZM137,47.68h7.74c8.08,0 13.21,-4.43 13.21,-11.12 0,-7.57 -5.48,-10.87 -13.47,-11a30.92,30.92 0,0 0,-7.48 0.7Z" android:strokeAlpha="0.75"/>
10</vector>
diff --git a/src/android/app/src/main/res/layout-w600dp/activity_main.xml b/src/android/app/src/main/res/layout-w600dp/activity_main.xml
new file mode 100644
index 000000000..74bee872e
--- /dev/null
+++ b/src/android/app/src/main/res/layout-w600dp/activity_main.xml
@@ -0,0 +1,58 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:id="@+id/coordinator_main"
7 android:layout_width="match_parent"
8 android:layout_height="match_parent"
9 android:background="?attr/colorSurface">
10
11 <androidx.fragment.app.FragmentContainerView
12 android:id="@+id/fragment_container"
13 android:name="androidx.navigation.fragment.NavHostFragment"
14 android:layout_width="0dp"
15 android:layout_height="0dp"
16 app:defaultNavHost="true"
17 app:layout_constraintBottom_toBottomOf="parent"
18 app:layout_constraintEnd_toEndOf="parent"
19 app:layout_constraintStart_toStartOf="parent"
20 app:layout_constraintTop_toTopOf="parent"
21 app:navGraph="@navigation/home_navigation"
22 tools:layout="@layout/fragment_games" />
23
24 <com.google.android.material.navigationrail.NavigationRailView
25 android:id="@+id/navigation_view"
26 android:layout_width="wrap_content"
27 android:layout_height="match_parent"
28 android:visibility="invisible"
29 app:layout_constraintBottom_toBottomOf="parent"
30 app:layout_constraintStart_toStartOf="parent"
31 app:layout_constraintTop_toTopOf="parent"
32 app:labelVisibilityMode="selected"
33 app:menu="@menu/menu_navigation"
34 tools:visibility="visible" />
35
36 <View
37 android:id="@+id/status_bar_shade"
38 android:layout_width="0dp"
39 android:layout_height="1px"
40 android:background="@android:color/transparent"
41 android:clickable="false"
42 android:focusable="false"
43 app:layout_constraintTop_toTopOf="parent"
44 app:layout_constraintEnd_toEndOf="parent"
45 app:layout_constraintStart_toStartOf="parent" />
46
47 <View
48 android:id="@+id/navigation_bar_shade"
49 android:layout_width="0dp"
50 android:layout_height="1px"
51 android:background="@android:color/transparent"
52 android:clickable="false"
53 android:focusable="false"
54 app:layout_constraintBottom_toBottomOf="parent"
55 app:layout_constraintEnd_toEndOf="parent"
56 app:layout_constraintStart_toStartOf="parent" />
57
58</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
new file mode 100644
index 000000000..cbe631d88
--- /dev/null
+++ b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
@@ -0,0 +1,40 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/setup_root"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent">
8
9 <androidx.viewpager2.widget.ViewPager2
10 android:id="@+id/viewPager2"
11 android:layout_width="0dp"
12 android:layout_height="0dp"
13 app:layout_constraintBottom_toBottomOf="parent"
14 app:layout_constraintEnd_toEndOf="parent"
15 app:layout_constraintStart_toStartOf="parent"
16 app:layout_constraintTop_toTopOf="parent" />
17
18 <com.google.android.material.button.MaterialButton
19 style="@style/Widget.Material3.Button.TextButton"
20 android:id="@+id/button_next"
21 android:layout_width="wrap_content"
22 android:layout_height="wrap_content"
23 android:layout_margin="16dp"
24 android:text="@string/next"
25 android:visibility="invisible"
26 app:layout_constraintBottom_toBottomOf="parent"
27 app:layout_constraintEnd_toEndOf="parent" />
28
29 <com.google.android.material.button.MaterialButton
30 android:id="@+id/button_back"
31 style="@style/Widget.Material3.Button.TextButton"
32 android:layout_width="wrap_content"
33 android:layout_height="wrap_content"
34 android:layout_margin="16dp"
35 android:text="@string/back"
36 android:visibility="invisible"
37 app:layout_constraintBottom_toBottomOf="parent"
38 app:layout_constraintStart_toStartOf="parent" />
39
40</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout-w600dp/page_setup.xml b/src/android/app/src/main/res/layout-w600dp/page_setup.xml
new file mode 100644
index 000000000..e1c26b2f8
--- /dev/null
+++ b/src/android/app/src/main/res/layout-w600dp/page_setup.xml
@@ -0,0 +1,65 @@
1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent">
8
9 <LinearLayout
10 android:layout_width="match_parent"
11 android:layout_height="match_parent"
12 android:orientation="vertical"
13 android:layout_weight="1"
14 android:gravity="center">
15
16 <ImageView
17 android:id="@+id/icon"
18 android:layout_width="260dp"
19 android:layout_height="260dp"
20 android:layout_gravity="center" />
21
22 </LinearLayout>
23
24 <LinearLayout
25 android:layout_width="match_parent"
26 android:layout_height="match_parent"
27 android:layout_weight="1"
28 android:orientation="vertical"
29 android:gravity="center">
30
31 <com.google.android.material.textview.MaterialTextView
32 style="@style/TextAppearance.Material3.DisplaySmall"
33 android:id="@+id/text_title"
34 android:layout_width="match_parent"
35 android:layout_height="wrap_content"
36 android:textAlignment="center"
37 android:textColor="?attr/colorOnSurface"
38 android:textStyle="bold"
39 tools:text="@string/welcome" />
40
41 <com.google.android.material.textview.MaterialTextView
42 style="@style/TextAppearance.Material3.TitleLarge"
43 android:id="@+id/text_description"
44 android:layout_width="match_parent"
45 android:layout_height="wrap_content"
46 android:layout_marginTop="16dp"
47 android:paddingHorizontal="32dp"
48 android:textAlignment="center"
49 android:textSize="26sp"
50 app:lineHeight="40sp"
51 tools:text="@string/welcome_description" />
52
53 <com.google.android.material.button.MaterialButton
54 android:id="@+id/button_action"
55 android:layout_width="wrap_content"
56 android:layout_height="56dp"
57 android:layout_marginTop="32dp"
58 android:textSize="20sp"
59 app:iconSize="24sp"
60 app:iconGravity="end"
61 tools:text="Get started" />
62
63 </LinearLayout>
64
65</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/activity_emulation.xml b/src/android/app/src/main/res/layout/activity_emulation.xml
new file mode 100644
index 000000000..f6360a65b
--- /dev/null
+++ b/src/android/app/src/main/res/layout/activity_emulation.xml
@@ -0,0 +1,13 @@
1<FrameLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:id="@+id/frame_content"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:keepScreenOn="true">
7
8 <FrameLayout
9 android:id="@+id/frame_emulation_fragment"
10 android:layout_width="match_parent"
11 android:layout_height="match_parent" />
12
13</FrameLayout>
diff --git a/src/android/app/src/main/res/layout/activity_main.xml b/src/android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..ad426457f
--- /dev/null
+++ b/src/android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,58 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:id="@+id/coordinator_main"
7 android:layout_width="match_parent"
8 android:layout_height="match_parent"
9 android:background="?attr/colorSurface">
10
11 <androidx.fragment.app.FragmentContainerView
12 android:id="@+id/fragment_container"
13 android:name="androidx.navigation.fragment.NavHostFragment"
14 android:layout_width="0dp"
15 android:layout_height="0dp"
16 app:defaultNavHost="true"
17 app:layout_constraintBottom_toBottomOf="parent"
18 app:layout_constraintLeft_toLeftOf="parent"
19 app:layout_constraintRight_toRightOf="parent"
20 app:layout_constraintTop_toTopOf="parent"
21 app:navGraph="@navigation/home_navigation"
22 tools:layout="@layout/fragment_games" />
23
24 <com.google.android.material.bottomnavigation.BottomNavigationView
25 android:id="@+id/navigation_view"
26 android:layout_width="match_parent"
27 android:layout_height="wrap_content"
28 android:visibility="invisible"
29 app:layout_constraintBottom_toBottomOf="parent"
30 app:layout_constraintLeft_toLeftOf="parent"
31 app:layout_constraintRight_toRightOf="parent"
32 app:menu="@menu/menu_navigation"
33 app:labelVisibilityMode="selected"
34 tools:visibility="visible" />
35
36 <View
37 android:id="@+id/status_bar_shade"
38 android:layout_width="0dp"
39 android:layout_height="1px"
40 android:background="@android:color/transparent"
41 android:clickable="false"
42 android:focusable="false"
43 app:layout_constraintTop_toTopOf="parent"
44 app:layout_constraintEnd_toEndOf="parent"
45 app:layout_constraintStart_toStartOf="parent" />
46
47 <View
48 android:id="@+id/navigation_bar_shade"
49 android:layout_width="0dp"
50 android:layout_height="1px"
51 android:background="@android:color/transparent"
52 android:clickable="false"
53 android:focusable="false"
54 app:layout_constraintBottom_toBottomOf="parent"
55 app:layout_constraintEnd_toEndOf="parent"
56 app:layout_constraintStart_toStartOf="parent" />
57
58</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 000000000..14ae83b04
--- /dev/null
+++ b/src/android/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,50 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout
3 android:id="@+id/coordinator_main"
4 xmlns:android="http://schemas.android.com/apk/res/android"
5 xmlns:app="http://schemas.android.com/apk/res-auto"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 android:background="?attr/colorSurface">
9
10 <com.google.android.material.appbar.AppBarLayout
11 android:id="@+id/appbar_settings"
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:fitsSystemWindows="true"
15 app:elevation="0dp">
16
17 <com.google.android.material.appbar.CollapsingToolbarLayout
18 style="?attr/collapsingToolbarLayoutMediumStyle"
19 android:id="@+id/toolbar_settings_layout"
20 android:layout_width="match_parent"
21 android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
22 app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
23
24 <com.google.android.material.appbar.MaterialToolbar
25 android:id="@+id/toolbar_settings"
26 android:layout_width="match_parent"
27 android:layout_height="?attr/actionBarSize"
28 app:layout_collapseMode="pin" />
29
30 </com.google.android.material.appbar.CollapsingToolbarLayout>
31
32 </com.google.android.material.appbar.AppBarLayout>
33
34 <FrameLayout
35 android:id="@+id/frame_content"
36 android:layout_width="match_parent"
37 android:layout_height="match_parent"
38 android:layout_marginHorizontal="12dp"
39 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
40
41 <View
42 android:id="@+id/navigation_bar_shade"
43 android:layout_width="match_parent"
44 android:layout_height="1px"
45 android:background="@android:color/transparent"
46 android:clickable="false"
47 android:focusable="false"
48 android:layout_gravity="bottom|center_horizontal" />
49
50</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml
new file mode 100644
index 000000000..1f5de219b
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_game.xml
@@ -0,0 +1,67 @@
1<?xml version="1.0" encoding="utf-8"?>
2<FrameLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:layout_width="match_parent"
7 android:layout_height="wrap_content">
8
9 <com.google.android.material.card.MaterialCardView
10 style="?attr/materialCardViewElevatedStyle"
11 android:id="@+id/card_game"
12 android:layout_width="wrap_content"
13 android:layout_height="wrap_content"
14 android:background="?attr/selectableItemBackground"
15 android:clickable="true"
16 android:clipToPadding="false"
17 android:focusable="true"
18 android:transitionName="card_game"
19 android:layout_gravity="center"
20 app:cardElevation="0dp"
21 app:cardCornerRadius="12dp">
22
23 <androidx.constraintlayout.widget.ConstraintLayout
24 android:layout_width="wrap_content"
25 android:layout_height="wrap_content"
26 android:padding="6dp">
27
28 <com.google.android.material.card.MaterialCardView
29 style="?attr/materialCardViewElevatedStyle"
30 android:id="@+id/card_game_art"
31 android:layout_width="150dp"
32 android:layout_height="150dp"
33 app:cardCornerRadius="4dp"
34 app:layout_constraintEnd_toEndOf="parent"
35 app:layout_constraintStart_toStartOf="parent"
36 app:layout_constraintTop_toTopOf="parent">
37
38 <ImageView
39 android:id="@+id/image_game_screen"
40 android:layout_width="match_parent"
41 android:layout_height="match_parent"
42 tools:src="@drawable/default_icon" />
43
44 </com.google.android.material.card.MaterialCardView>
45
46 <com.google.android.material.textview.MaterialTextView
47 style="@style/TextAppearance.Material3.TitleMedium"
48 android:id="@+id/text_game_title"
49 android:layout_width="0dp"
50 android:layout_height="wrap_content"
51 android:layout_marginTop="8dp"
52 android:textAlignment="center"
53 android:textSize="14sp"
54 android:singleLine="true"
55 android:marqueeRepeatLimit="marquee_forever"
56 android:ellipsize="none"
57 android:requiresFadingEdge="horizontal"
58 app:layout_constraintEnd_toEndOf="@+id/card_game_art"
59 app:layout_constraintStart_toStartOf="@+id/card_game_art"
60 app:layout_constraintTop_toBottomOf="@+id/card_game_art"
61 tools:text="The Legend of Zelda: Skyward Sword" />
62
63 </androidx.constraintlayout.widget.ConstraintLayout>
64
65 </com.google.android.material.card.MaterialCardView>
66
67</FrameLayout>
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml
new file mode 100644
index 000000000..dc289db17
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_home_option.xml
@@ -0,0 +1,60 @@
1<?xml version="1.0" encoding="utf-8"?>
2<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 style="?attr/materialCardViewFilledStyle"
6 android:id="@+id/option_card"
7 android:layout_width="match_parent"
8 android:layout_height="wrap_content"
9 android:layout_marginVertical="12dp"
10 android:layout_marginHorizontal="16dp"
11 android:background="?attr/selectableItemBackground"
12 android:backgroundTint="?attr/colorSurfaceVariant"
13 android:clickable="true"
14 android:focusable="true">
15
16 <LinearLayout
17 android:id="@+id/option_layout"
18 android:layout_width="match_parent"
19 android:layout_height="wrap_content">
20
21 <ImageView
22 android:id="@+id/option_icon"
23 android:layout_width="24dp"
24 android:layout_height="24dp"
25 android:layout_marginStart="24dp"
26 android:layout_gravity="center_vertical"
27 app:tint="?attr/colorOnSurface" />
28
29 <LinearLayout
30 android:layout_width="match_parent"
31 android:layout_height="wrap_content"
32 android:layout_marginVertical="10dp"
33 android:layout_marginHorizontal="20dp"
34 android:orientation="vertical">
35
36 <com.google.android.material.textview.MaterialTextView
37 style="@style/TextAppearance.Material3.BodyMedium"
38 android:id="@+id/option_title"
39 android:layout_width="match_parent"
40 android:layout_height="wrap_content"
41 android:textAlignment="viewStart"
42 android:textStyle="bold"
43 android:textSize="16sp"
44 tools:text="@string/install_prod_keys" />
45
46 <com.google.android.material.textview.MaterialTextView
47 style="@style/TextAppearance.Material3.BodySmall"
48 android:id="@+id/option_description"
49 android:layout_width="match_parent"
50 android:layout_height="wrap_content"
51 android:textAlignment="viewStart"
52 android:textSize="14sp"
53 android:layout_marginTop="5dp"
54 tools:text="@string/install_prod_keys_description" />
55
56 </LinearLayout>
57
58 </LinearLayout>
59
60</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/dialog_edit_text.xml b/src/android/app/src/main/res/layout/dialog_edit_text.xml
new file mode 100644
index 000000000..58b905d71
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_edit_text.xml
@@ -0,0 +1,23 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent">
7
8 <com.google.android.material.textfield.TextInputLayout
9 android:id="@+id/edit_text_layout"
10 android:layout_width="match_parent"
11 android:layout_height="wrap_content"
12 android:layout_margin="24dp"
13 app:layout_constraintTop_toTopOf="parent">
14
15 <com.google.android.material.textfield.TextInputEditText
16 android:id="@+id/edit_text"
17 android:layout_width="match_parent"
18 android:layout_height="wrap_content"
19 android:inputType="none" />
20
21 </com.google.android.material.textfield.TextInputLayout>
22
23</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_license.xml b/src/android/app/src/main/res/layout/dialog_license.xml
new file mode 100644
index 000000000..866857562
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_license.xml
@@ -0,0 +1,64 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 xmlns:tools="http://schemas.android.com/tools">
6
7 <androidx.core.widget.NestedScrollView
8 android:layout_width="match_parent"
9 android:layout_height="wrap_content">
10
11 <LinearLayout
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:orientation="vertical"
15 android:layout_marginHorizontal="16dp">
16
17 <com.google.android.material.bottomsheet.BottomSheetDragHandleView
18 android:layout_width="wrap_content"
19 android:layout_height="wrap_content"
20 android:layout_gravity="center_horizontal"/>
21
22 <com.google.android.material.textview.MaterialTextView
23 style="@style/TextAppearance.Material3.HeadlineLarge"
24 android:id="@+id/text_title"
25 android:layout_width="match_parent"
26 android:layout_height="wrap_content"
27 android:gravity="center"
28 tools:text="@string/license_adreno_tools" />
29
30 <com.google.android.material.textview.MaterialTextView
31 style="@style/TextAppearance.Material3.BodyLarge"
32 android:id="@+id/text_link"
33 android:layout_width="match_parent"
34 android:layout_height="wrap_content"
35 android:gravity="center"
36 android:layout_marginTop="16dp"
37 android:autoLink="all"
38 tools:text="@string/license_adreno_tools_link" />
39
40 <com.google.android.material.textview.MaterialTextView
41 style="@style/TextAppearance.Material3.BodyLarge"
42 android:id="@+id/text_copyright"
43 android:layout_width="match_parent"
44 android:layout_height="wrap_content"
45 android:gravity="center"
46 android:layout_marginTop="16dp"
47 android:textStyle="bold"
48 tools:text="@string/license_adreno_tools_copyright" />
49
50 <com.google.android.material.textview.MaterialTextView
51 style="@style/TextAppearance.Material3.BodyMedium"
52 android:id="@+id/text_license"
53 android:layout_width="match_parent"
54 android:layout_height="wrap_content"
55 android:layout_gravity="center"
56 android:layout_marginVertical="16dp"
57 android:autoLink="all"
58 tools:text="@string/license_adreno_tools_text" />
59
60 </LinearLayout>
61
62 </androidx.core.widget.NestedScrollView>
63
64</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_overlay_adjust.xml b/src/android/app/src/main/res/layout/dialog_overlay_adjust.xml
new file mode 100644
index 000000000..59bb983e1
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_overlay_adjust.xml
@@ -0,0 +1,67 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:tools="http://schemas.android.com/tools"
5 xmlns:app="http://schemas.android.com/apk/res-auto"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent">
8
9 <TextView
10 android:id="@+id/input_scale_name"
11 android:layout_width="wrap_content"
12 android:layout_height="wrap_content"
13 android:layout_marginTop="16dp"
14 android:text="@string/emulation_control_scale"
15 android:textAlignment="viewStart"
16 android:textSize="16sp"
17 app:layout_constraintStart_toStartOf="@+id/input_scale_slider"
18 app:layout_constraintTop_toTopOf="parent" />
19
20 <com.google.android.material.slider.Slider
21 android:id="@+id/input_scale_slider"
22 android:layout_width="0dp"
23 android:layout_height="wrap_content"
24 android:layout_marginHorizontal="24dp"
25 app:layout_constraintEnd_toEndOf="parent"
26 app:layout_constraintStart_toStartOf="parent"
27 app:layout_constraintTop_toBottomOf="@+id/input_scale_name" />
28
29 <TextView
30 android:id="@+id/input_scale_value"
31 android:layout_width="wrap_content"
32 android:layout_height="wrap_content"
33 android:gravity="end"
34 app:layout_constraintBottom_toTopOf="@+id/input_scale_slider"
35 app:layout_constraintEnd_toEndOf="@+id/input_scale_slider"
36 tools:text="100%" />
37
38 <TextView
39 android:id="@+id/input_opacity_name"
40 android:layout_width="wrap_content"
41 android:layout_height="wrap_content"
42 android:layout_marginTop="16dp"
43 android:text="@string/emulation_control_opacity"
44 android:textAlignment="viewStart"
45 android:textSize="16sp"
46 app:layout_constraintStart_toStartOf="@+id/input_opacity_slider"
47 app:layout_constraintTop_toBottomOf="@+id/input_scale_slider" />
48
49 <com.google.android.material.slider.Slider
50 android:id="@+id/input_opacity_slider"
51 android:layout_width="0dp"
52 android:layout_height="wrap_content"
53 android:layout_marginHorizontal="24dp"
54 app:layout_constraintEnd_toEndOf="parent"
55 app:layout_constraintStart_toStartOf="parent"
56 app:layout_constraintTop_toBottomOf="@+id/input_opacity_name" />
57
58 <TextView
59 android:id="@+id/input_opacity_value"
60 android:layout_width="wrap_content"
61 android:layout_height="wrap_content"
62 android:gravity="end"
63 app:layout_constraintBottom_toTopOf="@+id/input_opacity_slider"
64 app:layout_constraintEnd_toEndOf="@+id/input_opacity_slider"
65 tools:text="100%" />
66
67</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_progress_bar.xml b/src/android/app/src/main/res/layout/dialog_progress_bar.xml
new file mode 100644
index 000000000..d17711a65
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_progress_bar.xml
@@ -0,0 +1,24 @@
1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 xmlns:app="http://schemas.android.com/apk/res-auto"
6 android:orientation="vertical">
7
8 <com.google.android.material.progressindicator.LinearProgressIndicator
9 android:id="@+id/progress_bar"
10 android:layout_width="match_parent"
11 android:layout_height="wrap_content"
12 android:layout_margin="24dp"
13 app:trackCornerRadius="4dp" />
14
15 <TextView
16 android:id="@+id/progress_text"
17 android:layout_width="match_parent"
18 android:layout_height="wrap_content"
19 android:layout_marginLeft="24dp"
20 android:layout_marginRight="24dp"
21 android:layout_marginBottom="24dp"
22 android:gravity="end" />
23
24</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_slider.xml b/src/android/app/src/main/res/layout/dialog_slider.xml
new file mode 100644
index 000000000..8c84cb606
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_slider.xml
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="wrap_content"
6 android:orientation="vertical">
7
8 <TextView
9 android:id="@+id/text_value"
10 android:layout_width="wrap_content"
11 android:layout_height="wrap_content"
12 android:layout_alignParentTop="true"
13 android:layout_centerHorizontal="true"
14 android:layout_marginBottom="@dimen/spacing_medlarge"
15 android:layout_marginTop="@dimen/spacing_medlarge"
16 tools:text="75" />
17
18 <TextView
19 android:id="@+id/text_units"
20 android:layout_width="wrap_content"
21 android:layout_height="wrap_content"
22 android:layout_alignTop="@+id/text_value"
23 android:layout_toEndOf="@+id/text_value"
24 tools:text="%" />
25
26 <com.google.android.material.slider.Slider
27 android:id="@+id/slider"
28 android:layout_width="match_parent"
29 android:layout_height="wrap_content"
30 android:layout_alignParentEnd="true"
31 android:layout_alignParentStart="true"
32 android:layout_below="@+id/text_value"
33 android:layout_marginBottom="@dimen/spacing_medlarge"
34 android:layout_marginLeft="@dimen/spacing_large"
35 android:layout_marginRight="@dimen/spacing_large" />
36
37</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_about.xml b/src/android/app/src/main/res/layout/fragment_about.xml
new file mode 100644
index 000000000..3e1d98451
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_about.xml
@@ -0,0 +1,232 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:id="@+id/coordinator_about"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 android:background="?attr/colorSurface">
9
10 <com.google.android.material.appbar.AppBarLayout
11 android:id="@+id/appbar_about"
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:fitsSystemWindows="true">
15
16 <com.google.android.material.appbar.MaterialToolbar
17 android:id="@+id/toolbar_about"
18 android:layout_width="match_parent"
19 android:layout_height="?attr/actionBarSize"
20 app:title="@string/about"
21 app:navigationIcon="@drawable/ic_back" />
22
23 </com.google.android.material.appbar.AppBarLayout>
24
25 <androidx.core.widget.NestedScrollView
26 android:id="@+id/scroll_about"
27 android:layout_width="match_parent"
28 android:layout_height="match_parent"
29 android:scrollbars="vertical"
30 android:fadeScrollbars="false"
31 app:layout_behavior="@string/appbar_scrolling_view_behavior">
32
33 <LinearLayout
34 android:id="@+id/content_about"
35 android:layout_width="match_parent"
36 android:layout_height="match_parent"
37 android:orientation="vertical">
38
39 <ImageView
40 android:id="@+id/image_logo"
41 android:layout_width="250dp"
42 android:layout_height="250dp"
43 android:layout_marginTop="20dp"
44 android:layout_gravity="center_horizontal"
45 android:src="@drawable/ic_yuzu_title" />
46
47 <com.google.android.material.divider.MaterialDivider
48 android:layout_width="match_parent"
49 android:layout_height="wrap_content"
50 android:layout_marginHorizontal="20dp"
51 android:layout_marginTop="28dp" />
52
53 <LinearLayout
54 android:layout_width="match_parent"
55 android:layout_height="wrap_content"
56 android:paddingVertical="16dp"
57 android:paddingHorizontal="16dp"
58 android:orientation="vertical">
59
60 <com.google.android.material.textview.MaterialTextView
61 style="@style/TextAppearance.Material3.TitleMedium"
62 android:layout_width="match_parent"
63 android:layout_height="wrap_content"
64 android:layout_marginHorizontal="24dp"
65 android:textAlignment="viewStart"
66 android:text="@string/about" />
67
68 <com.google.android.material.textview.MaterialTextView
69 style="@style/TextAppearance.Material3.BodyMedium"
70 android:layout_width="match_parent"
71 android:layout_height="wrap_content"
72 android:layout_marginHorizontal="24dp"
73 android:layout_marginTop="6dp"
74 android:textAlignment="viewStart"
75 android:text="@string/about_app_description" />
76
77 </LinearLayout>
78
79 <com.google.android.material.divider.MaterialDivider
80 android:layout_width="match_parent"
81 android:layout_height="wrap_content"
82 android:layout_marginHorizontal="20dp" />
83
84 <LinearLayout
85 android:id="@+id/button_contributors"
86 android:layout_width="match_parent"
87 android:layout_height="wrap_content"
88 android:paddingVertical="16dp"
89 android:paddingHorizontal="16dp"
90 android:background="?attr/selectableItemBackground"
91 android:orientation="vertical">
92
93 <com.google.android.material.textview.MaterialTextView
94 style="@style/TextAppearance.Material3.TitleMedium"
95 android:layout_width="match_parent"
96 android:layout_height="wrap_content"
97 android:layout_marginHorizontal="24dp"
98 android:textAlignment="viewStart"
99 android:text="@string/contributors" />
100
101 <com.google.android.material.textview.MaterialTextView
102 style="@style/TextAppearance.Material3.BodyMedium"
103 android:layout_width="match_parent"
104 android:layout_height="wrap_content"
105 android:layout_marginHorizontal="24dp"
106 android:layout_marginTop="6dp"
107 android:textAlignment="viewStart"
108 android:text="@string/contributors_description" />
109
110 </LinearLayout>
111
112 <com.google.android.material.divider.MaterialDivider
113 android:layout_width="match_parent"
114 android:layout_height="wrap_content"
115 android:layout_marginHorizontal="20dp" />
116
117 <LinearLayout
118 android:id="@+id/button_licenses"
119 android:layout_width="match_parent"
120 android:layout_height="wrap_content"
121 android:paddingVertical="16dp"
122 android:paddingHorizontal="16dp"
123 android:background="?attr/selectableItemBackground"
124 android:orientation="vertical">
125
126 <com.google.android.material.textview.MaterialTextView
127 style="@style/TextAppearance.Material3.TitleMedium"
128 android:layout_width="match_parent"
129 android:layout_height="wrap_content"
130 android:layout_marginHorizontal="24dp"
131 android:textAlignment="viewStart"
132 android:text="@string/licenses" />
133
134 <com.google.android.material.textview.MaterialTextView
135 style="@style/TextAppearance.Material3.BodyMedium"
136 android:layout_width="match_parent"
137 android:layout_height="wrap_content"
138 android:layout_marginHorizontal="24dp"
139 android:layout_marginTop="6dp"
140 android:textAlignment="viewStart"
141 android:text="@string/licenses_description" />
142
143 </LinearLayout>
144
145 <com.google.android.material.divider.MaterialDivider
146 android:layout_width="match_parent"
147 android:layout_height="wrap_content"
148 android:layout_marginHorizontal="20dp" />
149
150 <LinearLayout
151 android:id="@+id/button_build_hash"
152 android:layout_width="match_parent"
153 android:layout_height="wrap_content"
154 android:paddingVertical="16dp"
155 android:paddingHorizontal="16dp"
156 android:background="?attr/selectableItemBackground"
157 android:orientation="vertical">
158
159 <com.google.android.material.textview.MaterialTextView
160 style="@style/TextAppearance.Material3.TitleMedium"
161 android:layout_width="match_parent"
162 android:layout_height="wrap_content"
163 android:layout_marginHorizontal="24dp"
164 android:textAlignment="viewStart"
165 android:text="@string/build" />
166
167 <com.google.android.material.textview.MaterialTextView
168 android:id="@+id/text_build_hash"
169 style="@style/TextAppearance.Material3.BodyMedium"
170 android:layout_width="match_parent"
171 android:layout_height="wrap_content"
172 android:layout_marginHorizontal="24dp"
173 android:layout_marginTop="6dp"
174 android:textAlignment="viewStart"
175 tools:text="abc123" />
176
177 </LinearLayout>
178
179 <com.google.android.material.divider.MaterialDivider
180 android:layout_width="match_parent"
181 android:layout_height="wrap_content"
182 android:layout_marginHorizontal="20dp" />
183
184 <LinearLayout
185 android:layout_width="match_parent"
186 android:layout_height="wrap_content"
187 android:orientation="horizontal"
188 android:gravity="center_horizontal"
189 android:layout_marginTop="12dp"
190 android:layout_marginBottom="16dp"
191 android:layout_marginHorizontal="40dp">
192
193 <Button
194 style="?attr/materialIconButtonStyle"
195 android:id="@+id/button_discord"
196 android:layout_width="0dp"
197 android:layout_height="wrap_content"
198 android:layout_weight="1"
199 app:icon="@drawable/ic_discord"
200 app:iconTint="?attr/colorOnSurface"
201 app:iconSize="24dp"
202 app:iconGravity="textEnd" />
203
204 <Button
205 style="?attr/materialIconButtonStyle"
206 android:id="@+id/button_website"
207 android:layout_width="0dp"
208 android:layout_height="wrap_content"
209 android:layout_weight="1"
210 app:icon="@drawable/ic_website"
211 app:iconTint="?attr/colorOnSurface"
212 app:iconSize="24dp"
213 app:iconGravity="textEnd" />
214
215 <Button
216 android:id="@+id/button_github"
217 style="?attr/materialIconButtonStyle"
218 android:layout_width="0dp"
219 android:layout_height="wrap_content"
220 android:layout_weight="1"
221 app:icon="@drawable/ic_github"
222 app:iconTint="?attr/colorOnSurface"
223 app:iconSize="24dp"
224 app:iconGravity="textEnd" />
225
226 </LinearLayout>
227
228 </LinearLayout>
229
230 </androidx.core.widget.NestedScrollView>
231
232</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_early_access.xml b/src/android/app/src/main/res/layout/fragment_early_access.xml
new file mode 100644
index 000000000..644b4dd45
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_early_access.xml
@@ -0,0 +1,242 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/coordinator_about"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 android:background="?attr/colorSurface">
9
10 <com.google.android.material.appbar.AppBarLayout
11 android:id="@+id/appbar_ea"
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:fitsSystemWindows="true">
15
16 <com.google.android.material.appbar.MaterialToolbar
17 android:id="@+id/toolbar_about"
18 android:layout_width="match_parent"
19 android:layout_height="?attr/actionBarSize"
20 app:navigationIcon="@drawable/ic_back"
21 app:title="@string/early_access" />
22
23 </com.google.android.material.appbar.AppBarLayout>
24
25 <androidx.core.widget.NestedScrollView
26 android:id="@+id/scroll_ea"
27 android:layout_width="match_parent"
28 android:layout_height="match_parent"
29 android:clipToPadding="false"
30 android:paddingBottom="20dp"
31 android:scrollbars="vertical"
32 android:fadeScrollbars="false"
33 app:layout_behavior="@string/appbar_scrolling_view_behavior">
34
35 <LinearLayout
36 android:id="@+id/card_ea"
37 android:layout_width="match_parent"
38 android:layout_height="match_parent"
39 android:layout_marginVertical="32dp"
40 android:layout_marginHorizontal="20dp"
41 android:background="@drawable/premium_background"
42 android:orientation="vertical">
43
44 <com.google.android.material.textview.MaterialTextView
45 style="@style/TextAppearance.Material3.TitleLarge"
46 android:layout_width="match_parent"
47 android:layout_height="wrap_content"
48 android:layout_marginTop="16dp"
49 android:layout_marginHorizontal="20dp"
50 android:text="@string/early_access_benefits"
51 android:textAlignment="center"
52 android:textStyle="bold" />
53
54 <LinearLayout
55 android:layout_width="match_parent"
56 android:layout_height="wrap_content"
57 android:layout_marginTop="32dp"
58 android:layout_marginHorizontal="20dp"
59 android:orientation="horizontal">
60
61 <ImageView
62 android:layout_width="24dp"
63 android:layout_height="24dp"
64 android:layout_gravity="center_vertical"
65 android:src="@drawable/ic_check_circle"
66 app:tint="?attr/colorOnSurface" />
67
68 <com.google.android.material.textview.MaterialTextView
69 style="@style/TextAppearance.Material3.BodyLarge"
70 android:layout_width="match_parent"
71 android:layout_height="wrap_content"
72 android:layout_marginStart="20dp"
73 android:text="@string/cutting_edge_features"
74 android:textAlignment="viewStart"
75 android:layout_gravity="start|center_vertical" />
76
77 </LinearLayout>
78
79 <LinearLayout
80 android:layout_width="match_parent"
81 android:layout_height="wrap_content"
82 android:layout_marginTop="32dp"
83 android:layout_marginHorizontal="20dp"
84 android:orientation="horizontal">
85
86 <ImageView
87 android:layout_width="24dp"
88 android:layout_height="24dp"
89 android:layout_gravity="center_vertical"
90 android:src="@drawable/ic_check_circle"
91 app:tint="?attr/colorOnSurface" />
92
93 <com.google.android.material.textview.MaterialTextView
94 style="@style/TextAppearance.Material3.BodyLarge"
95 android:layout_width="match_parent"
96 android:layout_height="wrap_content"
97 android:layout_marginStart="20dp"
98 android:text="@string/early_access_updates"
99 android:textAlignment="viewStart"
100 android:layout_gravity="start|center_vertical" />
101
102 </LinearLayout>
103
104 <LinearLayout
105 android:layout_width="match_parent"
106 android:layout_height="wrap_content"
107 android:layout_marginTop="32dp"
108 android:layout_marginHorizontal="20dp"
109 android:orientation="horizontal">
110
111 <ImageView
112 android:layout_width="24dp"
113 android:layout_height="24dp"
114 android:layout_gravity="center_vertical"
115 android:src="@drawable/ic_check_circle"
116 app:tint="?attr/colorOnSurface" />
117
118 <com.google.android.material.textview.MaterialTextView
119 style="@style/TextAppearance.Material3.BodyLarge"
120 android:layout_width="match_parent"
121 android:layout_height="wrap_content"
122 android:layout_marginStart="20dp"
123 android:text="@string/no_manual_installation"
124 android:textAlignment="viewStart"
125 android:layout_gravity="start|center_vertical" />
126
127 </LinearLayout>
128
129 <LinearLayout
130 android:layout_width="match_parent"
131 android:layout_height="wrap_content"
132 android:layout_marginTop="32dp"
133 android:layout_marginHorizontal="20dp"
134 android:orientation="horizontal">
135
136 <ImageView
137 android:layout_width="24dp"
138 android:layout_height="24dp"
139 android:layout_gravity="center_vertical"
140 android:src="@drawable/ic_check_circle"
141 app:tint="?attr/colorOnSurface" />
142
143 <com.google.android.material.textview.MaterialTextView
144 style="@style/TextAppearance.Material3.BodyLarge"
145 android:layout_width="match_parent"
146 android:layout_height="wrap_content"
147 android:layout_marginStart="20dp"
148 android:text="@string/prioritized_support"
149 android:textAlignment="viewStart"
150 android:layout_gravity="start|center_vertical" />
151
152 </LinearLayout>
153
154 <LinearLayout
155 android:layout_width="match_parent"
156 android:layout_height="wrap_content"
157 android:layout_marginTop="32dp"
158 android:layout_marginHorizontal="20dp"
159 android:orientation="horizontal">
160
161 <ImageView
162 android:layout_width="24dp"
163 android:layout_height="24dp"
164 android:layout_gravity="center_vertical"
165 android:src="@drawable/ic_check_circle"
166 app:tint="?attr/colorOnSurface" />
167
168 <com.google.android.material.textview.MaterialTextView
169 style="@style/TextAppearance.Material3.BodyLarge"
170 android:layout_width="match_parent"
171 android:layout_height="wrap_content"
172 android:layout_marginStart="20dp"
173 android:text="@string/helping_game_preservation"
174 android:textAlignment="viewStart"
175 android:layout_gravity="start|center_vertical" />
176
177 </LinearLayout>
178
179 <LinearLayout
180 android:layout_width="match_parent"
181 android:layout_height="wrap_content"
182 android:layout_marginTop="32dp"
183 android:layout_marginHorizontal="20dp"
184 android:orientation="horizontal">
185
186 <ImageView
187 android:layout_width="24dp"
188 android:layout_height="24dp"
189 android:layout_gravity="center_vertical"
190 android:src="@drawable/ic_check_circle"
191 app:tint="?attr/colorOnSurface" />
192
193 <com.google.android.material.textview.MaterialTextView
194 style="@style/TextAppearance.Material3.BodyLarge"
195 android:layout_width="match_parent"
196 android:layout_height="wrap_content"
197 android:layout_marginStart="20dp"
198 android:text="@string/our_eternal_gratitude"
199 android:textAlignment="viewStart"
200 android:layout_gravity="start|center_vertical" />
201
202 </LinearLayout>
203
204 <com.google.android.material.textview.MaterialTextView
205 style="@style/TextAppearance.Material3.TitleLarge"
206 android:layout_width="match_parent"
207 android:layout_height="wrap_content"
208 android:text="@string/are_you_interested"
209 android:layout_marginTop="80dp"
210 android:layout_marginHorizontal="20dp"
211 android:textStyle="bold"
212 android:textAlignment="center" />
213
214 <com.google.android.material.card.MaterialCardView
215 style="?attr/materialCardViewFilledStyle"
216 android:id="@+id/get_early_access_button"
217 android:layout_width="match_parent"
218 android:layout_height="wrap_content"
219 android:layout_marginTop="16dp"
220 android:layout_marginHorizontal="20dp"
221 android:layout_marginBottom="28dp"
222 android:background="?attr/selectableItemBackground"
223 android:backgroundTint="@android:color/black">
224
225 <com.google.android.material.textview.MaterialTextView
226 style="@style/TextAppearance.Material3.TitleLarge"
227 android:layout_width="match_parent"
228 android:layout_height="wrap_content"
229 android:text="@string/get_early_access"
230 android:layout_marginHorizontal="20dp"
231 android:layout_marginVertical="8dp"
232 android:textColor="@android:color/white"
233 android:textStyle="bold"
234 android:textAlignment="center" />
235
236 </com.google.android.material.card.MaterialCardView>
237
238 </LinearLayout>
239
240 </androidx.core.widget.NestedScrollView>
241
242</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml
new file mode 100644
index 000000000..09b789b6b
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -0,0 +1,70 @@
1<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:app="http://schemas.android.com/apk/res-auto"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:id="@+id/drawer_layout"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:keepScreenOn="true"
8 tools:context="org.yuzu.yuzu_emu.fragments.EmulationFragment"
9 tools:openDrawer="start">
10
11 <androidx.coordinatorlayout.widget.CoordinatorLayout
12 android:layout_width="match_parent"
13 android:layout_height="match_parent">
14
15 <!-- This is what everything is rendered to during emulation -->
16 <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView
17 android:id="@+id/surface_emulation"
18 android:layout_width="match_parent"
19 android:layout_height="match_parent"
20 android:layout_gravity="center"
21 android:focusable="false"
22 android:focusableInTouchMode="false" />
23
24 <FrameLayout
25 android:id="@+id/overlay_container"
26 android:layout_width="match_parent"
27 android:layout_height="match_parent"
28 android:layout_gravity="bottom">
29
30 <!-- This is the onscreen input overlay -->
31 <org.yuzu.yuzu_emu.overlay.InputOverlay
32 android:id="@+id/surface_input_overlay"
33 android:layout_width="match_parent"
34 android:layout_height="match_parent"
35 android:focusable="true"
36 android:focusableInTouchMode="true" />
37
38 <TextView
39 android:id="@+id/show_fps_text"
40 android:layout_width="wrap_content"
41 android:layout_height="wrap_content"
42 android:layout_gravity="left"
43 android:clickable="false"
44 android:focusable="false"
45 android:shadowColor="@android:color/black"
46 android:textColor="@android:color/white"
47 android:textSize="12sp"
48 tools:ignore="RtlHardcoded" />
49
50 <Button
51 style="@style/Widget.Material3.Button.ElevatedButton"
52 android:id="@+id/done_control_config"
53 android:layout_width="wrap_content"
54 android:layout_height="wrap_content"
55 android:layout_gravity="center"
56 android:text="@string/emulation_done"
57 android:visibility="gone" />
58 </FrameLayout>
59
60 </androidx.coordinatorlayout.widget.CoordinatorLayout>
61
62 <com.google.android.material.navigation.NavigationView
63 android:id="@+id/in_game_menu"
64 android:layout_width="wrap_content"
65 android:layout_height="match_parent"
66 android:layout_gravity="start|bottom"
67 app:headerLayout="@layout/header_in_game"
68 app:menu="@menu/menu_in_game" />
69
70</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_games.xml b/src/android/app/src/main/res/layout/fragment_games.xml
new file mode 100644
index 000000000..a0568668a
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_games.xml
@@ -0,0 +1,34 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:id="@+id/swipe_refresh"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 android:background="?attr/colorSurface"
9 android:clipToPadding="false">
10
11 <RelativeLayout
12 android:layout_width="match_parent"
13 android:layout_height="match_parent">
14
15 <com.google.android.material.textview.MaterialTextView
16 android:id="@+id/notice_text"
17 style="@style/TextAppearance.Material3.BodyLarge"
18 android:layout_width="match_parent"
19 android:layout_height="match_parent"
20 android:gravity="center"
21 android:padding="@dimen/spacing_large"
22 android:text="@string/empty_gamelist"
23 android:visibility="gone" />
24
25 <androidx.recyclerview.widget.RecyclerView
26 android:id="@+id/grid_games"
27 android:layout_width="match_parent"
28 android:layout_height="match_parent"
29 android:clipToPadding="false"
30 tools:listitem="@layout/card_game" />
31
32 </RelativeLayout>
33
34</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_home_settings.xml b/src/android/app/src/main/res/layout/fragment_home_settings.xml
new file mode 100644
index 000000000..1cb421dcb
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_home_settings.xml
@@ -0,0 +1,34 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.core.widget.NestedScrollView
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:id="@+id/scroll_view_settings"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface"
8 android:scrollbars="vertical"
9 android:fadeScrollbars="false"
10 android:clipToPadding="false">
11
12 <androidx.appcompat.widget.LinearLayoutCompat
13 android:id="@+id/linear_layout_settings"
14 android:layout_width="match_parent"
15 android:layout_height="match_parent"
16 android:orientation="vertical"
17 android:background="?attr/colorSurface">
18
19 <ImageView
20 android:id="@+id/logo_image"
21 android:layout_width="128dp"
22 android:layout_height="128dp"
23 android:layout_margin="64dp"
24 android:layout_gravity="center_horizontal"
25 android:src="@drawable/ic_yuzu_full" />
26
27 <androidx.recyclerview.widget.RecyclerView
28 android:id="@+id/home_settings_list"
29 android:layout_width="match_parent"
30 android:layout_height="match_parent" />
31
32 </androidx.appcompat.widget.LinearLayoutCompat>
33
34</androidx.core.widget.NestedScrollView>
diff --git a/src/android/app/src/main/res/layout/fragment_licenses.xml b/src/android/app/src/main/res/layout/fragment_licenses.xml
new file mode 100644
index 000000000..6b31ff5b4
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_licenses.xml
@@ -0,0 +1,30 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/coordinator_licenses"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface">
8
9 <com.google.android.material.appbar.AppBarLayout
10 android:id="@+id/appbar_licenses"
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:fitsSystemWindows="true">
14
15 <com.google.android.material.appbar.MaterialToolbar
16 android:id="@+id/toolbar_licenses"
17 android:layout_width="match_parent"
18 android:layout_height="?attr/actionBarSize"
19 app:title="@string/licenses"
20 app:navigationIcon="@drawable/ic_back" />
21
22 </com.google.android.material.appbar.AppBarLayout>
23
24 <androidx.recyclerview.widget.RecyclerView
25 android:id="@+id/list_licenses"
26 android:layout_width="match_parent"
27 android:layout_height="match_parent"
28 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
29
30</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_search.xml b/src/android/app/src/main/res/layout/fragment_search.xml
new file mode 100644
index 000000000..b8d54d947
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_search.xml
@@ -0,0 +1,183 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:id="@+id/constraint_search"
7 android:layout_width="match_parent"
8 android:layout_height="match_parent"
9 android:background="?attr/colorSurface"
10 android:clipToPadding="false">
11
12 <RelativeLayout
13 android:layout_width="0dp"
14 android:layout_height="0dp"
15 app:layout_constraintBottom_toBottomOf="parent"
16 app:layout_constraintEnd_toEndOf="parent"
17 app:layout_constraintStart_toStartOf="parent"
18 app:layout_constraintTop_toBottomOf="@+id/divider">
19
20 <LinearLayout
21 android:id="@+id/no_results_view"
22 android:layout_width="match_parent"
23 android:layout_height="match_parent"
24 android:orientation="vertical"
25 android:gravity="center">
26
27 <ImageView
28 android:id="@+id/icon_no_results"
29 android:layout_width="match_parent"
30 android:layout_height="80dp"
31 android:src="@drawable/ic_search" />
32
33 <com.google.android.material.textview.MaterialTextView
34 android:id="@+id/notice_text"
35 style="@style/TextAppearance.Material3.TitleLarge"
36 android:layout_width="match_parent"
37 android:layout_height="wrap_content"
38 android:gravity="center"
39 android:paddingTop="8dp"
40 android:text="@string/search_and_filter_games"
41 tools:visibility="visible" />
42
43 </LinearLayout>
44
45 <androidx.recyclerview.widget.RecyclerView
46 android:id="@+id/grid_games_search"
47 android:layout_width="match_parent"
48 android:layout_height="match_parent"
49 android:clipToPadding="false" />
50
51 </RelativeLayout>
52
53 <FrameLayout
54 android:id="@+id/frame_search"
55 android:layout_width="match_parent"
56 android:layout_height="wrap_content"
57 android:layout_marginTop="12dp"
58 android:layout_marginHorizontal="20dp"
59 app:layout_constraintEnd_toEndOf="parent"
60 app:layout_constraintStart_toStartOf="parent"
61 app:layout_constraintTop_toTopOf="parent">
62
63 <com.google.android.material.card.MaterialCardView
64 android:id="@+id/search_background"
65 style="?attr/materialCardViewFilledStyle"
66 android:layout_width="match_parent"
67 android:layout_height="56dp"
68 app:cardCornerRadius="28dp">
69
70 <LinearLayout
71 android:id="@+id/search_container"
72 android:layout_width="match_parent"
73 android:layout_height="match_parent"
74 android:layout_marginStart="24dp"
75 android:layout_marginEnd="56dp"
76 android:orientation="horizontal">
77
78 <ImageView
79 android:layout_width="28dp"
80 android:layout_height="28dp"
81 android:layout_gravity="center_vertical"
82 android:layout_marginEnd="24dp"
83 android:src="@drawable/ic_search"
84 app:tint="?attr/colorOnSurfaceVariant" />
85
86 <EditText
87 android:id="@+id/search_text"
88 android:layout_width="match_parent"
89 android:layout_height="match_parent"
90 android:background="@android:color/transparent"
91 android:hint="@string/home_search_games"
92 android:inputType="text"
93 android:maxLines="1"
94 android:imeOptions="flagNoFullscreen" />
95
96 </LinearLayout>
97
98 <ImageView
99 android:id="@+id/clear_button"
100 android:layout_width="24dp"
101 android:layout_height="24dp"
102 android:layout_gravity="center_vertical|end"
103 android:layout_marginEnd="24dp"
104 android:background="?attr/selectableItemBackground"
105 android:src="@drawable/ic_clear"
106 android:visibility="invisible"
107 app:tint="?attr/colorOnSurfaceVariant"
108 tools:visibility="visible" />
109
110 </com.google.android.material.card.MaterialCardView>
111
112 </FrameLayout>
113
114 <HorizontalScrollView
115 android:id="@+id/horizontalScrollView"
116 android:layout_width="match_parent"
117 android:layout_height="wrap_content"
118 android:fadingEdge="horizontal"
119 android:scrollbars="none"
120 app:layout_constraintEnd_toEndOf="parent"
121 app:layout_constraintStart_toStartOf="parent"
122 app:layout_constraintTop_toBottomOf="@+id/frame_search">
123
124 <com.google.android.material.chip.ChipGroup
125 android:id="@+id/chip_group"
126 android:layout_width="wrap_content"
127 android:layout_height="wrap_content"
128 android:clipToPadding="false"
129 android:paddingVertical="4dp"
130 app:chipSpacingHorizontal="12dp"
131 app:singleLine="true"
132 app:singleSelection="true">
133
134 <com.google.android.material.chip.Chip
135 android:id="@+id/chip_recently_played"
136 style="@style/Widget.Material3.Chip.Suggestion.Elevated"
137 android:layout_width="wrap_content"
138 android:layout_height="wrap_content"
139 android:checked="false"
140 android:text="@string/search_recently_played"
141 app:chipCornerRadius="28dp" />
142
143 <com.google.android.material.chip.Chip
144 android:id="@+id/chip_recently_added"
145 style="@style/Widget.Material3.Chip.Suggestion.Elevated"
146 android:layout_width="wrap_content"
147 android:layout_height="wrap_content"
148 android:checked="false"
149 android:text="@string/search_recently_added"
150 app:chipCornerRadius="28dp" />
151
152 <com.google.android.material.chip.Chip
153 android:id="@+id/chip_retail"
154 style="@style/Widget.Material3.Chip.Suggestion.Elevated"
155 android:layout_width="wrap_content"
156 android:layout_height="wrap_content"
157 android:checked="false"
158 android:text="@string/search_retail"
159 app:chipCornerRadius="28dp" />
160
161 <com.google.android.material.chip.Chip
162 android:id="@+id/chip_homebrew"
163 style="@style/Widget.Material3.Chip.Suggestion.Elevated"
164 android:layout_width="wrap_content"
165 android:layout_height="wrap_content"
166 android:checked="false"
167 android:text="@string/search_homebrew"
168 app:chipCornerRadius="28dp" />
169
170 </com.google.android.material.chip.ChipGroup>
171
172 </HorizontalScrollView>
173
174 <com.google.android.material.divider.MaterialDivider
175 android:id="@+id/divider"
176 android:layout_width="match_parent"
177 android:layout_height="wrap_content"
178 android:layout_marginHorizontal="20dp"
179 app:layout_constraintEnd_toEndOf="parent"
180 app:layout_constraintStart_toStartOf="parent"
181 app:layout_constraintTop_toBottomOf="@+id/horizontalScrollView" />
182
183</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_settings.xml b/src/android/app/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 000000000..167720347
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,14 @@
1<?xml version="1.0" encoding="utf-8"?>
2<FrameLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent">
6
7 <androidx.recyclerview.widget.RecyclerView
8 android:id="@+id/list_settings"
9 android:layout_width="match_parent"
10 android:layout_height="match_parent"
11 android:background="?attr/colorSurface"
12 android:clipToPadding="false" />
13
14</FrameLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_setup.xml b/src/android/app/src/main/res/layout/fragment_setup.xml
new file mode 100644
index 000000000..d7bafaea2
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_setup.xml
@@ -0,0 +1,42 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/setup_root"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent">
8
9 <androidx.viewpager2.widget.ViewPager2
10 android:id="@+id/viewPager2"
11 android:layout_width="0dp"
12 android:layout_height="0dp"
13 android:clipToPadding="false"
14 android:layout_marginBottom="16dp"
15 app:layout_constraintBottom_toTopOf="@+id/button_next"
16 app:layout_constraintEnd_toEndOf="parent"
17 app:layout_constraintStart_toStartOf="parent"
18 app:layout_constraintTop_toTopOf="parent" />
19
20 <com.google.android.material.button.MaterialButton
21 style="@style/Widget.Material3.Button.TextButton"
22 android:id="@+id/button_next"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:layout_margin="12dp"
26 android:text="@string/next"
27 android:visibility="invisible"
28 app:layout_constraintBottom_toBottomOf="parent"
29 app:layout_constraintEnd_toEndOf="parent" />
30
31 <com.google.android.material.button.MaterialButton
32 style="@style/Widget.Material3.Button.TextButton"
33 android:id="@+id/button_back"
34 android:layout_width="wrap_content"
35 android:layout_height="wrap_content"
36 android:layout_margin="12dp"
37 android:text="@string/back"
38 android:visibility="invisible"
39 app:layout_constraintBottom_toBottomOf="parent"
40 app:layout_constraintStart_toStartOf="parent" />
41
42</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/header_in_game.xml b/src/android/app/src/main/res/layout/header_in_game.xml
new file mode 100644
index 000000000..958cfb7e3
--- /dev/null
+++ b/src/android/app/src/main/res/layout/header_in_game.xml
@@ -0,0 +1,14 @@
1<?xml version="1.0" encoding="utf-8"?>
2<com.google.android.material.textview.MaterialTextView
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:id="@+id/text_game_title"
6 android:layout_width="match_parent"
7 android:layout_height="wrap_content"
8 android:layout_marginTop="24dp"
9 android:layout_marginStart="24dp"
10 android:layout_marginEnd="24dp"
11 android:textAppearance="?attr/textAppearanceHeadlineMedium"
12 android:textColor="?attr/colorOnSurface"
13 android:textAlignment="viewStart"
14 tools:text="Super Mario Odyssey" />
diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml
new file mode 100644
index 000000000..ec896342b
--- /dev/null
+++ b/src/android/app/src/main/res/layout/list_item_setting.xml
@@ -0,0 +1,41 @@
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="wrap_content"
6 xmlns:app="http://schemas.android.com/apk/res-auto"
7 android:background="?android:attr/selectableItemBackground"
8 android:clickable="true"
9 android:focusable="true"
10 android:gravity="center_vertical"
11 android:minHeight="72dp"
12 android:padding="@dimen/spacing_large">
13
14 <com.google.android.material.textview.MaterialTextView
15 style="@style/TextAppearance.Material3.HeadlineMedium"
16 android:id="@+id/text_setting_name"
17 android:layout_width="0dp"
18 android:layout_height="wrap_content"
19 android:layout_alignParentEnd="true"
20 android:layout_alignParentStart="true"
21 android:layout_alignParentTop="true"
22 android:textSize="16sp"
23 android:textAlignment="viewStart"
24 app:lineHeight="28dp"
25 tools:text="Setting Name" />
26
27 <TextView
28 style="@style/TextAppearance.Material3.BodySmall"
29 android:id="@+id/text_setting_description"
30 android:layout_width="wrap_content"
31 android:layout_height="wrap_content"
32 android:layout_alignParentEnd="true"
33 android:layout_alignParentStart="true"
34 android:layout_alignStart="@+id/text_setting_name"
35 android:layout_below="@+id/text_setting_name"
36 android:layout_marginTop="@dimen/spacing_small"
37 android:visibility="visible"
38 android:textAlignment="viewStart"
39 tools:text="@string/app_disclaimer" />
40
41</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_setting_switch.xml b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
new file mode 100644
index 000000000..599d845ad
--- /dev/null
+++ b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
@@ -0,0 +1,50 @@
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="wrap_content"
6 xmlns:app="http://schemas.android.com/apk/res-auto"
7 android:background="?android:attr/selectableItemBackground"
8 android:clickable="true"
9 android:focusable="true"
10 android:minHeight="72dp"
11 android:paddingStart="@dimen/spacing_large"
12 android:paddingEnd="24dp"
13 android:paddingVertical="@dimen/spacing_large">
14
15 <com.google.android.material.materialswitch.MaterialSwitch
16 android:id="@+id/switch_widget"
17 android:layout_width="wrap_content"
18 android:layout_height="wrap_content"
19 android:layout_alignParentEnd="true"
20 android:layout_centerVertical="true" />
21
22 <com.google.android.material.textview.MaterialTextView
23 style="@style/TextAppearance.Material3.BodySmall"
24 android:id="@+id/text_setting_description"
25 android:layout_width="wrap_content"
26 android:layout_height="wrap_content"
27 android:layout_alignParentStart="true"
28 android:layout_alignStart="@+id/text_setting_name"
29 android:layout_below="@+id/text_setting_name"
30 android:layout_marginEnd="@dimen/spacing_large"
31 android:layout_marginTop="@dimen/spacing_small"
32 android:layout_toStartOf="@+id/switch_widget"
33 android:textAlignment="viewStart"
34 tools:text="@string/frame_limit_enable_description" />
35
36 <com.google.android.material.textview.MaterialTextView
37 style="@style/TextAppearance.Material3.HeadlineMedium"
38 android:id="@+id/text_setting_name"
39 android:layout_width="0dp"
40 android:layout_height="wrap_content"
41 android:layout_alignParentStart="true"
42 android:layout_alignParentTop="true"
43 android:layout_marginEnd="@dimen/spacing_large"
44 android:layout_toStartOf="@+id/switch_widget"
45 android:textSize="16sp"
46 android:textAlignment="viewStart"
47 app:lineHeight="28dp"
48 tools:text="@string/frame_limit_enable" />
49
50</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_settings_header.xml b/src/android/app/src/main/res/layout/list_item_settings_header.xml
new file mode 100644
index 000000000..abd24df6f
--- /dev/null
+++ b/src/android/app/src/main/res/layout/list_item_settings_header.xml
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="utf-8"?>
2<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="48dp"
6 android:paddingVertical="4dp"
7 android:paddingHorizontal="@dimen/spacing_large">
8
9 <com.google.android.material.textview.MaterialTextView
10 style="@style/TextAppearance.Material3.TitleSmall"
11 android:id="@+id/text_header_name"
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:layout_gravity="start|center_vertical"
15 android:textColor="?attr/colorPrimary"
16 android:textAlignment="viewStart"
17 android:textStyle="bold"
18 tools:text="CPU Settings" />
19
20</FrameLayout>
diff --git a/src/android/app/src/main/res/layout/page_setup.xml b/src/android/app/src/main/res/layout/page_setup.xml
new file mode 100644
index 000000000..1436ef308
--- /dev/null
+++ b/src/android/app/src/main/res/layout/page_setup.xml
@@ -0,0 +1,72 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent">
8
9 <ImageView
10 android:id="@+id/icon"
11 android:layout_width="0dp"
12 android:layout_height="0dp"
13 android:layout_marginTop="64dp"
14 android:layout_marginBottom="32dp"
15 app:layout_constraintBottom_toTopOf="@+id/text_title"
16 app:layout_constraintEnd_toEndOf="parent"
17 app:layout_constraintHeight_max="220dp"
18 app:layout_constraintHeight_min="110dp"
19 app:layout_constraintStart_toStartOf="parent"
20 app:layout_constraintTop_toTopOf="parent"
21 app:layout_constraintVertical_chainStyle="spread"
22 app:layout_constraintWidth_max="220dp"
23 app:layout_constraintWidth_min="110dp"
24 app:layout_constraintVertical_weight="3" />
25
26 <com.google.android.material.textview.MaterialTextView
27 android:id="@+id/text_title"
28 style="@style/TextAppearance.Material3.DisplayMedium"
29 android:layout_width="0dp"
30 android:layout_height="0dp"
31 android:textAlignment="center"
32 android:textColor="?attr/colorOnSurface"
33 android:textStyle="bold"
34 app:layout_constraintBottom_toTopOf="@+id/text_description"
35 app:layout_constraintEnd_toEndOf="parent"
36 app:layout_constraintStart_toStartOf="parent"
37 app:layout_constraintTop_toBottomOf="@+id/icon"
38 app:layout_constraintVertical_weight="1.3"
39 tools:text="@string/welcome" />
40
41 <com.google.android.material.textview.MaterialTextView
42 android:id="@+id/text_description"
43 style="@style/TextAppearance.Material3.TitleLarge"
44 android:layout_width="0dp"
45 android:layout_height="0dp"
46 android:textAlignment="center"
47 android:textSize="26sp"
48 android:paddingHorizontal="16dp"
49 app:layout_constraintBottom_toTopOf="@+id/button_action"
50 app:layout_constraintEnd_toEndOf="parent"
51 app:layout_constraintStart_toStartOf="parent"
52 app:layout_constraintTop_toBottomOf="@+id/text_title"
53 app:layout_constraintVertical_weight="2"
54 app:lineHeight="40sp"
55 tools:text="@string/welcome_description" />
56
57 <com.google.android.material.button.MaterialButton
58 android:id="@+id/button_action"
59 android:layout_width="wrap_content"
60 android:layout_height="56dp"
61 android:textSize="20sp"
62 android:layout_marginTop="16dp"
63 android:layout_marginBottom="48dp"
64 app:iconGravity="end"
65 app:iconSize="24sp"
66 app:layout_constraintBottom_toBottomOf="parent"
67 app:layout_constraintEnd_toEndOf="parent"
68 app:layout_constraintStart_toStartOf="parent"
69 app:layout_constraintTop_toBottomOf="@+id/text_description"
70 tools:text="Get started" />
71
72</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/menu-w600dp/menu_navigation.xml b/src/android/app/src/main/res/menu-w600dp/menu_navigation.xml
new file mode 100644
index 000000000..dd7698e78
--- /dev/null
+++ b/src/android/app/src/main/res/menu-w600dp/menu_navigation.xml
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<menu xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <item
5 android:id="@+id/homeSettingsFragment"
6 android:icon="@drawable/selector_settings"
7 android:title="@string/home_settings" />
8
9 <item
10 android:id="@+id/searchFragment"
11 android:icon="@drawable/ic_search"
12 android:title="@string/home_search" />
13
14 <item
15 android:id="@+id/gamesFragment"
16 android:icon="@drawable/selector_cartridge"
17 android:title="@string/home_games" />
18
19</menu>
diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml
new file mode 100644
index 000000000..f98f727b6
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_in_game.xml
@@ -0,0 +1,24 @@
1<?xml version="1.0" encoding="utf-8"?>
2<menu xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <item
5 android:id="@+id/menu_pause_emulation"
6 android:icon="@drawable/ic_pause"
7 android:title="@string/emulation_pause" />
8
9 <item
10 android:id="@+id/menu_settings"
11 android:icon="@drawable/ic_settings"
12 android:title="@string/preferences_settings" />
13
14 <item
15 android:id="@+id/menu_overlay_controls"
16 android:icon="@drawable/ic_controller"
17 android:title="@string/emulation_input_overlay" />
18
19 <item
20 android:id="@+id/menu_exit"
21 android:icon="@drawable/ic_exit"
22 android:title="@string/emulation_exit" />
23
24</menu>
diff --git a/src/android/app/src/main/res/menu/menu_navigation.xml b/src/android/app/src/main/res/menu/menu_navigation.xml
new file mode 100644
index 000000000..da128c5a1
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_navigation.xml
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<menu xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <item
5 android:id="@+id/gamesFragment"
6 android:icon="@drawable/selector_cartridge"
7 android:title="@string/home_games" />
8
9 <item
10 android:id="@+id/searchFragment"
11 android:icon="@drawable/ic_search"
12 android:title="@string/home_search" />
13
14 <item
15 android:id="@+id/homeSettingsFragment"
16 android:icon="@drawable/selector_settings"
17 android:title="@string/home_settings" />
18
19</menu>
diff --git a/src/android/app/src/main/res/menu/menu_overlay_options.xml b/src/android/app/src/main/res/menu/menu_overlay_options.xml
new file mode 100644
index 000000000..4885b4f6f
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_overlay_options.xml
@@ -0,0 +1,45 @@
1<?xml version="1.0" encoding="utf-8"?>
2<menu xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <item
5 android:id="@+id/menu_toggle_fps"
6 android:title="@string/emulation_fps_counter"
7 android:checkable="true" />
8
9 <item
10 android:id="@+id/menu_edit_overlay"
11 android:title="@string/emulation_touch_overlay_edit" />
12
13 <item
14 android:id="@+id/menu_adjust_overlay"
15 android:title="@string/emulation_control_adjust" />
16
17 <item
18 android:id="@+id/menu_toggle_controls"
19 android:title="@string/emulation_toggle_controls" />
20
21 <item
22 android:id="@+id/menu_show_overlay"
23 android:title="@string/emulation_show_overlay"
24 android:checkable="true" />
25
26 <item
27 android:id="@+id/menu_rel_stick_center"
28 android:title="@string/emulation_rel_stick_center"
29 android:checkable="true" />
30
31 <item
32 android:id="@+id/menu_dpad_slide"
33 android:title="@string/emulation_dpad_slide"
34 android:checkable="true" />
35
36 <item
37 android:id="@+id/menu_haptics"
38 android:title="@string/emulation_haptics"
39 android:checkable="true" />
40
41 <item
42 android:id="@+id/menu_reset_overlay"
43 android:title="@string/emulation_touch_overlay_reset" />
44
45</menu>
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml
new file mode 100644
index 000000000..1fe7aa6d4
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_settings.xml
@@ -0,0 +1,2 @@
1<?xml version="1.0" encoding="utf-8"?>
2<menu /> \ No newline at end of file
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
new file mode 100644
index 000000000..48072683e
--- /dev/null
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -0,0 +1,59 @@
1<?xml version="1.0" encoding="utf-8"?>
2<navigation xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/home_navigation"
5 app:startDestination="@id/gamesFragment">
6
7 <fragment
8 android:id="@+id/gamesFragment"
9 android:name="org.yuzu.yuzu_emu.ui.GamesFragment"
10 android:label="PlatformGamesFragment" />
11
12 <fragment
13 android:id="@+id/homeSettingsFragment"
14 android:name="org.yuzu.yuzu_emu.fragments.HomeSettingsFragment"
15 android:label="HomeSettingsFragment" >
16 <action
17 android:id="@+id/action_homeSettingsFragment_to_aboutFragment"
18 app:destination="@id/aboutFragment" />
19 <action
20 android:id="@+id/action_homeSettingsFragment_to_earlyAccessFragment"
21 app:destination="@id/earlyAccessFragment" />
22 </fragment>
23
24 <fragment
25 android:id="@+id/firstTimeSetupFragment"
26 android:name="org.yuzu.yuzu_emu.fragments.SetupFragment"
27 android:label="FirstTimeSetupFragment" >
28 <action
29 android:id="@+id/action_firstTimeSetupFragment_to_gamesFragment"
30 app:destination="@id/gamesFragment"
31 app:popUpTo="@id/firstTimeSetupFragment"
32 app:popUpToInclusive="true" />
33 </fragment>
34
35 <fragment
36 android:id="@+id/searchFragment"
37 android:name="org.yuzu.yuzu_emu.fragments.SearchFragment"
38 android:label="SearchFragment" />
39
40 <fragment
41 android:id="@+id/aboutFragment"
42 android:name="org.yuzu.yuzu_emu.fragments.AboutFragment"
43 android:label="AboutFragment" >
44 <action
45 android:id="@+id/action_aboutFragment_to_licensesFragment"
46 app:destination="@id/licensesFragment" />
47 </fragment>
48
49 <fragment
50 android:id="@+id/earlyAccessFragment"
51 android:name="org.yuzu.yuzu_emu.fragments.EarlyAccessFragment"
52 android:label="EarlyAccessFragment" />
53
54 <fragment
55 android:id="@+id/licensesFragment"
56 android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment"
57 android:label="LicensesFragment" />
58
59</navigation>
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml
new file mode 100644
index 000000000..969223ef8
--- /dev/null
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,332 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Diese Software kann Spiele für die Nintendo Switch abspielen. Keine Spiele oder Spielekeys sind enthalten.&lt;br /&gt;&lt;br /&gt;Bevor du beginnst, bitte halte deine <![CDATA[<b> prod.keys </b>]]> auf deinem Gerät bereit. .&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Mehr Infos</a>]]></string>
5 <string name="emulation_notification_channel_name">Emulation ist aktiv</string>
6 <string name="emulation_notification_channel_description">Zeigt eine dauerhafte Benachrichtigung an, wenn die Emulation läuft.</string>
7 <string name="emulation_notification_running">yuzu läuft</string>
8 <string name="notice_notification_channel_name">Hinweise und Fehler</string>
9 <string name="notice_notification_channel_description">Zeigt Benachrichtigungen an, wenn etwas schief läuft.</string>
10 <string name="notification_permission_not_granted">Berechtigung für Benachrichtigungen nicht erlaubt!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Willkommen!</string>
14 <string name="welcome_description">Erfahre wie man &lt;b>yuzu&lt;/b> einrichtet und beginne mit der Emulation.</string>
15 <string name="get_started">Erste Schritte</string>
16 <string name="keys">Schlüssel</string>
17 <string name="keys_description">Wähle deine &lt;b>prod.keys&lt;/b> Datei mit dem Button unten aus.</string>
18 <string name="select_keys">Schlüssel auswählen</string>
19 <string name="games">Spiele</string>
20 <string name="games_description">Wähle mit dem Knopf unten den &lt;b>Spiele&lt;/b>-Ordner aus.</string>
21 <string name="done">Fertig</string>
22 <string name="done_description">Wir können loslegen.\nViel Spaß!</string>
23 <string name="text_continue">Fortsetzen</string>
24 <string name="next">Weiter</string>
25 <string name="back">Zurück</string>
26 <string name="add_games">Spiele hinzufügen</string>
27 <string name="add_games_description">Spieleverzeichnis auswählen</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Spiele</string>
31 <string name="home_search">Suche</string>
32 <string name="home_settings">Einstellungen</string>
33 <string name="empty_gamelist">Es wurden keine Dateien gefunden oder es wurde noch kein Spielverzeichnis ausgewählt.</string>
34 <string name="search_and_filter_games">Spiele suchen und filtern</string>
35 <string name="select_games_folder">Spieleverzeichnis auswählen</string>
36 <string name="select_games_folder_description">Erlaubt yuzu die Spieleliste zu füllen</string>
37 <string name="add_games_warning">Auswahl des Spieleverzeichnisses überspringen?</string>
38 <string name="add_games_warning_description">Spiele werden in der Spieleliste nicht angezeigt, wenn kein Ordner ausgewählt ist.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Spiele suchen</string>
41 <string name="games_dir_selected">Spieleverzeichnis ausgewählt</string>
42 <string name="install_prod_keys">prod.keys installieren</string>
43 <string name="install_prod_keys_description">Zum Entschlüsseln von Spielen benötigt</string>
44 <string name="install_prod_keys_warning">Hinzufügen der Schlüssel überspringen?</string>
45 <string name="install_prod_keys_warning_description">Für die Emulation von Spielen sind gültige Schlüssel erforderlich. Wenn du fortfährst, funktionieren nur Homebrew-Anwendungen.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Benachrichtigungen</string>
48 <string name="notifications_description">Erteile mit dem Knopf unten die Berechtigung, Benachrichtigungen zu senden.</string>
49 <string name="give_permission">Berechtigung erteilen</string>
50 <string name="notification_warning_description">yuzu wird dich nicht über wichtige Informationen benachrichtigen können.</string>
51 <string name="permission_denied">Zugriff verweigert</string>
52 <string name="permission_denied_description">Du hast diese Berechtigung zu oft verweigert und musst sie nun manuell in den Systemeinstellungen erteilen.</string>
53 <string name="about">Über</string>
54 <string name="about_description">Build-Version, Credits und mehr</string>
55 <string name="warning_help">Hilfe</string>
56 <string name="warning_skip">Überspringen</string>
57 <string name="warning_cancel">Abbrechen</string>
58 <string name="install_amiibo_keys">Amiibo-Schlüssel installieren</string>
59 <string name="install_amiibo_keys_description">Benötigt um Amiibos im Spiel zu verwenden</string>
60 <string name="invalid_keys_file">Ungültige Schlüsseldatei ausgewählt</string>
61 <string name="install_keys_success">Schlüssel erfolgreich installiert</string>
62 <string name="reading_keys_failure">Fehler beim Lesen der Schlüssel</string>
63 <string name="invalid_keys_error">Ungültige Schlüssel</string>
64 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
65 <string name="install_gpu_driver">GPU-Treiber installieren</string>
66 <string name="install_gpu_driver_description">Alternative Treiber für eventuell bessere Leistung oder Genauigkeit installieren</string>
67 <string name="advanced_settings">Erweiterte Einstellungen</string>
68 <string name="settings_description">Emulatoreinstellungen konfigurieren</string>
69 <string name="search_recently_played">Kürzlich gespielt</string>
70 <string name="search_recently_added">Kürzlich hinzugefügt</string>
71 <string name="search_retail">Spiele</string>
72 <string name="search_homebrew">Homebrew</string>
73 <string name="open_user_folder">yuzu-Ordner öffnen</string>
74 <string name="open_user_folder_description">yuzu\'s interne Dateien verwalten</string>
75 <string name="theme_and_color_description">Das Aussehen der App ändern</string>
76 <string name="no_file_manager">Kein Dateimanager gefunden</string>
77 <string name="notification_no_directory_link">yuzu-Verzeichnis konnte nicht geöffnet werden</string>
78 <string name="notification_no_directory_link_description">Bitte suche den Benutzerordner manuell über die Seitenleiste des Dateimanagers.</string>
79 <string name="manage_save_data">Speicherdaten verwalten</string>
80 <string name="manage_save_data_description">Speicherdaten gefunden. Bitte wähle unten eine Option aus.</string>
81 <string name="import_export_saves_description">Speicherdaten importieren oder exportieren</string>
82 <string name="import_export_saves_no_profile">Keine Speicherdaten gefunden. Bitte starte ein Spiel und versuche es erneut.</string>
83 <string name="save_file_imported_success">Erfolgreich importiert</string>
84 <string name="save_file_invalid_zip_structure">Ungültige Speicherverzeichnisstruktur</string>
85 <string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string>
86 <string name="import_saves">Importieren</string>
87 <string name="export_saves">Exportieren</string>
88
89 <!-- About screen strings -->
90 <string name="gaia_is_not_real">Gaia ist nicht real</string>
91 <string name="copied_to_clipboard">In die Zwischenablage kopiert</string>
92 <string name="about_app_description">Ein quelloffener Switch-Emulator</string>
93 <string name="contributors">Beitragende</string>
94 <string name="contributors_description">Gemacht mit \u2764 vom yuzu Team</string>
95 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
96 <string name="build">Build</string>
97 <string name="support_link">https://discord.gg/u77vRWY</string>
98 <string name="website_link">https://yuzu-emu.org/</string>
99 <string name="github_link">https://github.com/yuzu-emu</string>
100
101 <!-- Early access upgrade strings -->
102 <string name="early_access">Early Access</string>
103 <string name="get_early_access">Early Access bekommen</string>
104 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
105 <string name="get_early_access_description">Neueste Features, frühzeitiger Zugriff auf Updates und mehr</string>
106 <string name="early_access_benefits">Early Access Vorteile</string>
107 <string name="cutting_edge_features">Neueste Features</string>
108 <string name="early_access_updates">Früherer Zugriff auf Updates</string>
109 <string name="no_manual_installation">Keine manuelle Installation</string>
110 <string name="prioritized_support">Priorisierte Unterstützung</string>
111 <string name="our_eternal_gratitude">Unsere ewige Dankbarkeit</string>
112 <string name="are_you_interested">Bist du interessiert?</string>
113
114 <!-- General settings strings -->
115 <string name="frame_limit_enable">Geschwindigkeitsbegrenzung aktivieren</string>
116 <string name="frame_limit_enable_description">Wenn aktiviert, wird die Emulationsgeschwindigkeit auf einen Prozentsatz der normalen Geschwindigkeit begrenzt.</string>
117 <string name="frame_limit_slider">Geschwindkeitsbegrenzung in Prozent</string>
118 <string name="frame_limit_slider_description">Legt den Prozentsatz der Bergrenzung der Emulationsgeschwindigkeit fest. Mit dem Standardwert von 100% wird die Emulation auf die normale Geschwindigkeit begrenzt. Höhere oder niedrigere Werte erhöhen oder verringern die Geschwindigkeitsbegrenzung.</string>
119 <string name="cpu_accuracy">CPU-Genauigkeit</string>
120
121 <!-- System settings strings -->
122 <string name="use_docked_mode">Dock-Modus</string>
123 <string name="use_docked_mode_description">Emuliert im Dock-Modus, was die Auflösung verbessert, aber die Leistung senkt.</string>
124 <string name="emulated_region">Emulierte Region</string>
125 <string name="emulated_language">Emulierte Sprache</string>
126 <string name="select_rtc_date">RTC-Datum auswählen</string>
127 <string name="select_rtc_time">RTC-Zeit auswählen</string>
128 <string name="use_custom_rtc">Benutzerdefinierte RTC aktivieren</string>
129 <string name="use_custom_rtc_description">Mit dieser Einstellung kann eine benutzerdefinierte Echtzeituhr unabhängig von der aktuellen Systemzeit verwendet werden.</string>
130 <string name="set_custom_rtc">Benutzerdefinierte RTC einstellen</string>
131
132 <!-- Graphics settings strings -->
133 <string name="renderer_api">API</string>
134 <string name="renderer_accuracy">Genauigkeitsstufe</string>
135 <string name="renderer_resolution">Auflösung</string>
136 <string name="renderer_vsync">VSync-Modus</string>
137 <string name="renderer_aspect_ratio">Seitenverhältnis</string>
138 <string name="renderer_scaling_filter">Fensteranpassungsfilter</string>
139 <string name="renderer_anti_aliasing">Kantenglättungs-Methode</string>
140 <string name="renderer_force_max_clock">Maximale Taktfrequenz erzwingen (nur Adreno)</string>
141 <string name="renderer_force_max_clock_description">Erzwingt den Betrieb der GPU mit der maximal möglichen Taktfrequenz (Temperaturbeschränkungen werden weiterhin angewendet).</string>
142 <string name="renderer_asynchronous_shaders">Asynchrone Shader nutzen</string>
143 <string name="renderer_asynchronous_shaders_description">Kompiliert Shader asynchron, was Ruckler reduziert, aber zu Glitches führen kann.</string>
144 <string name="renderer_debug">Grafik-Debugging aktivieren</string>
145 <string name="renderer_debug_description">Wenn aktiviert, schaltet die Grafik-API in einen langsameren Debugging-Modus.</string>
146 <string name="use_disk_shader_cache">Nutze Festplatten-Shader-Cache</string>
147 <string name="use_disk_shader_cache_description">Ruckeln wird durch das Speichern und Laden von generierten Shadern auf der Festplatte reduziert.</string>
148
149 <!-- Audio settings strings -->
150 <string name="audio_volume">Lautstärke</string>
151 <string name="audio_volume_description">Legt die Lautstärke der Audioausgabe fest.</string>
152
153 <!-- Miscellaneous -->
154 <string name="slider_default">Standard</string>
155 <string name="ini_saved">Einstellungen gespeichert</string>
156 <string name="gameid_saved">Einstellungen für %1$s gespeichert</string>
157 <string name="error_saving">Fehler beim Speichern von %1$s.ini: %2$s</string>
158 <string name="loading">Lädt...</string>
159 <string name="reset_setting_confirmation">Möchtest du diese Einstellung auf den Standardwert zurücksetzen?</string>
160 <string name="reset_to_default">Auf Standard zurücksetzen</string>
161 <string name="reset_all_settings">Alle Einstellungen zurücksetzen?</string>
162 <string name="reset_all_settings_description">Alle erweiterten Einstellungen werden auf ihren Standardwert zurückgesetzt. Dies kann nicht rückgängig gemacht werden.</string>
163 <string name="settings_reset">Einstellungen zurückgesetzt</string>
164 <string name="close">Schließen</string>
165 <string name="learn_more">Mehr erfahren</string>
166
167 <!-- GPU driver installation -->
168 <string name="select_gpu_driver">GPU-Treiber auswählen</string>
169 <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
170 <string name="select_gpu_driver_install">Installieren</string>
171 <string name="select_gpu_driver_default">Standard</string>
172 <string name="select_gpu_driver_install_success">%s wurde installiert</string>
173 <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
174 <string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string>
175 <string name="system_gpu_driver">System GPU-Treiber</string>
176 <string name="installing_driver">Treiber wird installiert...</string>
177
178 <!-- Preferences Screen -->
179 <string name="preferences_settings">Einstellungen</string>
180 <string name="preferences_general">Allgemein</string>
181 <string name="preferences_system">System</string>
182 <string name="preferences_graphics">Grafik</string>
183 <string name="preferences_audio">Audio</string>
184 <string name="preferences_theme">Theme und Farbe</string>
185
186 <!-- ROM loading errors -->
187 <string name="loader_error_encrypted">Das ROM ist verschlüsselt</string>
188 <string name="loader_error_encrypted_keys_description"><![CDATA[Bitte stelle sicher dass die <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> Datei installiert ist, damit Spiele entschlüsselt werden können.]]></string>
189 <string name="loader_error_video_core">Bei der Initialisierung des Videokerns ist ein Fehler aufgetreten</string>
190 <string name="loader_error_video_core_description">Dies wird normalerweise durch einen inkompatiblen GPU-Treiber verursacht. Die Installation eines passenden GPU-Treibers kann dieses Problem beheben.</string>
191 <string name="loader_error_invalid_format">ROM konnte nicht geladen werden</string>
192 <string name="loader_error_file_not_found">ROM-Datei existiert nicht</string>
193
194 <!-- Emulation Menu -->
195 <string name="emulation_exit">Emulation beenden</string>
196 <string name="emulation_done">Fertig</string>
197 <string name="emulation_fps_counter">FPS Zähler</string>
198 <string name="emulation_toggle_controls">Steuerung umschalten</string>
199 <string name="emulation_rel_stick_center">Relative Stick-Mitte</string>
200 <string name="emulation_dpad_slide">DPad Slide</string>
201 <string name="emulation_haptics">Haptik</string>
202 <string name="emulation_show_overlay">Overlay anzeigen</string>
203 <string name="emulation_toggle_all">Alle umschalten</string>
204 <string name="emulation_control_adjust">Overlay anpassen</string>
205 <string name="emulation_control_scale">Größe</string>
206 <string name="emulation_control_opacity">Transparenz</string>
207 <string name="emulation_touch_overlay_reset">Overlay zurücksetzen</string>
208 <string name="emulation_touch_overlay_edit">Overlay bearbeiten</string>
209 <string name="emulation_pause">Emulation pausieren</string>
210 <string name="emulation_unpause">Emulation fortsetzen</string>
211 <string name="emulation_input_overlay">Overlay-Optionen</string>
212 <string name="emulation_game_loading">Spiel lädt…</string>
213
214 <string name="load_settings">Lädt Einstellungen...</string>
215
216 <!-- Software keyboard -->
217 <string name="software_keyboard">Software-Tastatur</string>
218
219 <!-- Errors and warnings -->
220 <string name="abort_button">Abbrechen</string>
221 <string name="continue_button">Fortsetzen</string>
222 <string name="system_archive_not_found">Systemarchiv nicht gefunden</string>
223 <string name="system_archive_general">Ein System-Archiv</string>
224 <string name="save_load_error">Speicher-/Ladefehler</string>
225 <string name="fatal_error">Schwerwiegender Fehler</string>
226 <string name="fatal_error_message">Ein schwerwiegender Fehler ist aufgetreten. Einzelheiten wurden im Log protokolliert.\nDas Fortsetzen der Emulation kann zu Abstürzen und Bugs führen.</string>
227 <string name="performance_warning">Das Deaktivieren dieser Einstellung führt zu erheblichen Leistungsverlusten! Für ein optimales Erlebnis wird empfohlen, sie aktiviert zu lassen.</string>
228
229 <!-- Region Names -->
230 <string name="region_japan">Japan</string>
231 <string name="region_usa">USA</string>
232 <string name="region_europe">Europa</string>
233 <string name="region_australia">Australien</string>
234 <string name="region_china">China</string>
235 <string name="region_korea">Korea</string>
236 <string name="region_taiwan">Taiwan</string>
237
238 <!-- Language Names -->
239 <string name="language_japanese">Japanisch (日本語)</string>
240 <string name="language_english">Englisch</string>
241 <string name="language_french">Französisch (Français)</string>
242 <string name="langauge_german">Deutsch (German)</string>
243 <string name="language_italian">Italienisch (Italiano)</string>
244 <string name="language_spanish">Spanisch (Español)</string>
245 <string name="language_chinese">Chinesisch (简体中文)</string>
246 <string name="language_korean">Koreanisch (한국어)</string>
247 <string name="language_dutch">Niederländisch (Nederlands)</string>
248 <string name="language_portuguese">Portugiesisch (Português)</string>
249 <string name="language_russian">Russisch (Русский)</string>
250 <string name="language_taiwanese">Taiwanesisch (台湾)</string>
251 <string name="language_british_english">Britisches Englisch</string>
252 <string name="language_canadian_french">Kanadisches Französisch (Français canadien)</string>
253 <string name="language_latin_american_spanish">Lateinamerikanisches Spanisch (Español latinoamericano)</string>
254 <string name="language_simplified_chinese">Vereinfachtes Chinesisch (简体中文)</string>
255 <string name="language_traditional_chinese">Traditionelles Chinesisch (正體中文)</string>
256 <string name="language_brazilian_portuguese">Brasilianisches Portugiesisch (Português do Brasil)</string>
257
258 <!-- Renderer APIs -->
259 <string name="renderer_vulkan">Vulkan</string>
260 <string name="renderer_none">Keiner</string>
261
262 <!-- Renderer Accuracy -->
263 <string name="renderer_accuracy_normal">Normal</string>
264 <string name="renderer_accuracy_high">Hoch</string>
265 <string name="renderer_accuracy_extreme">Extrem (Langsam)</string>
266
267 <!-- Resolutions -->
268 <string name="resolution_half">0.5X (360p/540p)</string>
269 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
270 <string name="resolution_one">1X (720p/1080p)</string>
271 <string name="resolution_two">2X (1440p/2160p) (Langsam)</string>
272 <string name="resolution_three">3X (2160p/3240p) (Langsam)</string>
273 <string name="resolution_four">4X (2880p/4320p) (Langsam)</string>
274
275 <!-- Renderer VSync -->
276 <string name="renderer_vsync_immediate">Direkt (Aus)</string>
277 <string name="renderer_vsync_mailbox">Mailbox</string>
278 <string name="renderer_vsync_fifo">FIFO (An)</string>
279 <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
280
281 <!-- Scaling Filters -->
282 <string name="scaling_filter_nearest_neighbor">Nächste-Nachbarn</string>
283 <string name="scaling_filter_bilinear">Bilinear</string>
284 <string name="scaling_filter_bicubic">Bikubisch</string>
285 <string name="scaling_filter_gaussian">Gaussian</string>
286 <string name="scaling_filter_scale_force">ScaleForce</string>
287 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
288
289 <!-- Anti-Aliasing -->
290 <string name="anti_aliasing_none">Keiner</string>
291 <string name="anti_aliasing_fxaa">FXAA</string>
292 <string name="anti_aliasing_smaa">SMAA</string>
293
294 <!-- Aspect Ratios -->
295 <string name="ratio_default">Standard (16:9)</string>
296 <string name="ratio_force_four_three">4:3 erzwingen</string>
297 <string name="ratio_force_twenty_one_nine">21:9 erzwingen</string>
298 <string name="ratio_force_sixteen_ten">Erzwinge 16:10</string>
299 <string name="ratio_stretch">Auf Fenster anpassen</string>
300
301 <!-- CPU Accuracy -->
302 <string name="cpu_accuracy_accurate">Akkurat</string>
303 <string name="cpu_accuracy_unsafe">Unsicher</string>
304 <string name="cpu_accuracy_paranoid">Paranoid (Langsam)</string>
305
306 <!-- Gamepad Buttons -->
307 <string name="gamepad_d_pad">Steuerkreuz</string>
308 <string name="gamepad_left_stick">Linker Analogstick</string>
309 <string name="gamepad_right_stick">Rechter Analogstick</string>
310 <string name="gamepad_home">Home</string>
311 <string name="gamepad_screenshot">Screenshot</string>
312
313 <!-- Disk shader cache -->
314 <string name="preparing_shaders">Shader werden vorbereitet</string>
315 <string name="building_shaders">Shader werden erstellt</string>
316
317 <!-- Theme options -->
318 <string name="change_app_theme">App-Theme ändern</string>
319 <string name="theme_default">Standard</string>
320 <string name="theme_material_you">Material You</string>
321
322 <!-- Theme Modes -->
323 <string name="change_theme_mode">Theme-Modus ändern</string>
324 <string name="theme_mode_follow_system">System folgen</string>
325 <string name="theme_mode_light">Hell</string>
326 <string name="theme_mode_dark">Dunkel</string>
327
328 <!-- Black backgrounds theme -->
329 <string name="use_black_backgrounds">Schwarze Hintergünde verwenden</string>
330 <string name="use_black_backgrounds_description">Bei Verwendung des dunklen Themes, schwarze Hintergründe verwenden.</string>
331
332</resources>
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
new file mode 100644
index 000000000..986e80e50
--- /dev/null
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Este software ejecuta juegos para la videoconsola Nintendo Switch. Los videojuegos o keys no vienen incluidos.&lt;br /&gt;&lt;br /&gt;Antes de empezar, por favor, localice el archivo <![CDATA[<b> prod.keys </b>]]>en el almacenamiento de su dispositivo..&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Saber más</a>]]></string>
5 <string name="emulation_notification_channel_name">Emulación activa</string>
6 <string name="emulation_notification_channel_description">Muestra una notificación persistente cuando la emulación está activa.</string>
7 <string name="emulation_notification_running">yuzu esta ejecutándose</string>
8 <string name="notice_notification_channel_name">Avisos y errores</string>
9 <string name="notice_notification_channel_description">Mostrar notificaciones cuándo algo vaya mal.</string>
10 <string name="notification_permission_not_granted">¡Permisos de notificación no concedidos!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">¡Bienvenido!</string>
14 <string name="welcome_description">Aprende cómo configurar &lt;b>yuzu&lt;/b> y avanza a la emulación.</string>
15 <string name="get_started">Empezar</string>
16 <string name="keys">Claves</string>
17 <string name="keys_description">Selecciona el archivo &lt;b>prod.keys&lt;/b> utilizando el botón de abajo.</string>
18 <string name="select_keys">Seleccionar las claves</string>
19 <string name="games">Juegos</string>
20 <string name="games_description">Selecciona la carpeta &lt;b>Games&lt;/b> utilizando el botón de abajo</string>
21 <string name="done">Hecho</string>
22 <string name="done_description">Todo listo.\n¡Disfrute de sus juegos!</string>
23 <string name="text_continue">Continuar</string>
24 <string name="next">Siguiente</string>
25 <string name="back">Atrás</string>
26 <string name="add_games">Añadir Juegos</string>
27 <string name="add_games_description">Selecciona la carpeta de juegos</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Juegos</string>
31 <string name="home_search">Buscar</string>
32 <string name="home_settings">Ajustes</string>
33 <string name="empty_gamelist">No se ha encontrado ningún archivo o aún no se ha seleccionado ningún directorio de juegos.</string>
34 <string name="search_and_filter_games">Busca y filtra juegos</string>
35 <string name="select_games_folder">Seleccionar carpeta de juegos</string>
36 <string name="select_games_folder_description">Permite que yuzu llene la lista de juegos</string>
37 <string name="add_games_warning">¿Omitir la selección de la carpeta de juegos?</string>
38 <string name="add_games_warning_description">No se mostrará ningún juego si no se ha seleccionado una carpeta de juegos.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Buscar Juegos</string>
41 <string name="games_dir_selected">Directorio de juegos seleccionado</string>
42 <string name="install_prod_keys">Instalar prod.keys</string>
43 <string name="install_prod_keys_description">Requerido para descifrar juegos</string>
44 <string name="install_prod_keys_warning">¿Omitir agregar claves?</string>
45 <string name="install_prod_keys_warning_description">Se requieren claves válidas para emular juegos. Solo las aplicaciones homebrew funcionarán si continúas.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Notificaciones</string>
48 <string name="notifications_description">Otorgue el permiso de notificación con el botón de abajo.</string>
49 <string name="give_permission">Conceder permiso</string>
50 <string name="notification_warning">¿Omitir conceder el permiso de notificación?</string>
51 <string name="notification_warning_description">yuzu no podrá notificarte información importante.</string>
52 <string name="permission_denied">Permiso denegado</string>
53 <string name="permission_denied_description">Negó este permiso demasiadas veces y ahora debe otorgarlo manualmente en la configuración del sistema.</string>
54 <string name="about">Acerca de</string>
55 <string name="about_description">Versión, créditos y más</string>
56 <string name="warning_help">Ayuda</string>
57 <string name="warning_skip">Siguiente</string>
58 <string name="warning_cancel">Cancelar</string>
59 <string name="install_amiibo_keys">Instalar clave de Amiiboo</string>
60 <string name="install_amiibo_keys_description">Necesario para usar Amiibo en el juego</string>
61 <string name="invalid_keys_file">Archivo de claves inválido seleccionado</string>
62 <string name="install_keys_success">Claves instaladas correctamente</string>
63 <string name="reading_keys_failure">Error al leer las claves de cifrado</string>
64 <string name="invalid_keys_error">Claves de cifrado no válidas</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">El archivo seleccionado es incorrecto o está corrupto. Vuelva a redumpear sus claves.</string>
67 <string name="install_gpu_driver">Instalar driver de GPU</string>
68 <string name="install_gpu_driver_description">Instale drivers alternativos para obtener un rendimiento o una precisión potencialmente mejores</string>
69 <string name="advanced_settings">Opciones avanzadas</string>
70 <string name="settings_description">Configurar las opciones del emulador</string>
71 <string name="search_recently_played">Jugado recientemente</string>
72 <string name="search_recently_added">Añadido recientemente</string>
73 <string name="search_retail">Juegos</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Abrir la carpeta de yuzu</string>
76 <string name="open_user_folder_description">Administrar los archivos internos de yuzu</string>
77 <string name="theme_and_color_description">Modificar la apariencia de la aplicación</string>
78 <string name="no_file_manager">Explorador de archivos no encontrado</string>
79 <string name="notification_no_directory_link">No se pudo abrir la carpeta yuzu</string>
80 <string name="notification_no_directory_link_description">Por favor, busque la carpeta user con el panel lateral del explorador de archivos de forma manual.</string>
81 <string name="manage_save_data">Administrar datos de guardado</string>
82 <string name="manage_save_data_description">Guardar los datos encontrados. Por favor, seleccione una opción de abajo.</string>
83 <string name="import_export_saves_description">Importar o exportar archivos de guardado</string>
84 <string name="import_export_saves_no_profile">No se han encontrado datos de guardado. Por favor, ejecute un juego y vuelva a intentarlo.</string>
85 <string name="save_file_imported_success">Importado correctamente</string>
86 <string name="save_file_invalid_zip_structure">Estructura del directorio de guardado no válido</string>
87 <string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string>
88 <string name="import_saves">Importar</string>
89 <string name="export_saves">Exportar</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia no es real</string>
93 <string name="copied_to_clipboard">Copiado al portapapeles</string>
94 <string name="about_app_description">Un emulador de Switch de código abierto</string>
95 <string name="contributors">Contribuidores</string>
96 <string name="contributors_description">Hecho con \u2764 del equipo yuzu</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Versión</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Early Access</string>
105 <string name="get_early_access">Conseguir Early Access</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Funciones de vanguardia, acceso anticipado a actualizaciones y más</string>
108 <string name="early_access_benefits">Beneficios Early Access</string>
109 <string name="cutting_edge_features">Características de vanguardia</string>
110 <string name="early_access_updates">Acceso anticipado a las actualizaciones</string>
111 <string name="no_manual_installation">Sin instalación manual</string>
112 <string name="prioritized_support">Soporte prioritario</string>
113 <string name="helping_game_preservation">Ayudarás a la preservación de juegos</string>
114 <string name="our_eternal_gratitude">Nuestra eterna gratitud</string>
115 <string name="are_you_interested">¿Estás interesado?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Activar limite de velocidad</string>
119 <string name="frame_limit_enable_description">Cuando está habilitado, la velocidad de emulación se limitará a un porcentaje específico de la velocidad normal.</string>
120 <string name="frame_limit_slider">Limitar porcentaje de velocidad</string>
121 <string name="frame_limit_slider_description">Especifica el porcentaje para limitar la velocidad de emulación. Con el valor predeterminado del 100 %, la emulación se limitará a la velocidad normal. Valores más altos o más bajos aumentarán o disminuirán el límite de velocidad.</string>
122 <string name="cpu_accuracy">Precisión de CPU</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Modo sobremesa</string>
126 <string name="use_docked_mode_description">Emula en modo sobremesa, lo que aumenta la resolución perjudicando el rendimiento.</string>
127 <string name="emulated_region">Región emulada</string>
128 <string name="emulated_language">Idioma emulado</string>
129 <string name="select_rtc_date">Seleccionar Fecha RTC</string>
130 <string name="select_rtc_time">Seleccionar Tiempo RTC</string>
131 <string name="use_custom_rtc">Habilitar RTC Personalizado</string>
132 <string name="use_custom_rtc_description">Esta configuración le permite configurar un reloj de tiempo real personalizado diferente a la hora actual de su sistema</string>
133 <string name="set_custom_rtc">Establecer RTC Personalizado</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">Nivel de precisión</string>
138 <string name="renderer_resolution">Resolución</string>
139 <string name="renderer_vsync">Modo VSync</string>
140 <string name="renderer_aspect_ratio">Relación de aspecto</string>
141 <string name="renderer_scaling_filter">Filtro de adaptación de ventana</string>
142 <string name="renderer_anti_aliasing">Metodo Anti Aliasing</string>
143 <string name="renderer_force_max_clock">Forzar velocidad al máximo (solo Adreno)</string>
144 <string name="renderer_force_max_clock_description">Fuerza a la GPU a ejecutarse a la velocidad máxima de reloj posible (se seguirán aplicando restricciones térmicas).</string>
145 <string name="renderer_asynchronous_shaders">Usar shaders asíncronos</string>
146 <string name="renderer_asynchronous_shaders_description">Compila shaders de forma asincrónica, lo que reducirá los parones pero puede introducir fallos.</string>
147 <string name="renderer_debug">Habilitar la depuración de gráficos</string>
148 <string name="renderer_debug_description">Cuando esté marcado, la API de gráficos entra en un modo de depuración más lento.</string>
149 <string name="use_disk_shader_cache">Usar caché de shaders en disco</string>
150 <string name="use_disk_shader_cache_description">Reduzca los parones almacenando y cargando shaders generados en el disco.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Volumen</string>
154 <string name="audio_volume_description">Especifica el volumen de la salida de audio.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">Predeterminado</string>
158 <string name="ini_saved">Configuración guardada</string>
159 <string name="gameid_saved">Configuración guardada para %1$s</string>
160 <string name="error_saving">Error guardando %1$s.ini: %2$s</string>
161 <string name="loading">Cargando...</string>
162 <string name="reset_setting_confirmation">¿Desea restablecer esta configuración a su valor predeterminado?</string>
163 <string name="reset_to_default">Restablecer a predeterminado</string>
164 <string name="reset_all_settings">¿Restablecer todas las configuraciones?</string>
165 <string name="reset_all_settings_description">Todas las configuraciones avanzadas se restablecerán a su configuración predeterminada. Esto no se puede deshacer.</string>
166 <string name="settings_reset">Reiniciar la configuracion</string>
167 <string name="close">Cerrar</string>
168 <string name="learn_more">Más información</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Seleccionar driver GPU</string>
172 <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
173 <string name="select_gpu_driver_install">Instalar</string>
174 <string name="select_gpu_driver_default">Predeterminado</string>
175 <string name="select_gpu_driver_install_success">Instalado %s</string>
176 <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
177 <string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string>
178 <string name="system_gpu_driver">Driver GPU del sistema</string>
179 <string name="installing_driver">Instalando driver...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Ajustes</string>
183 <string name="preferences_general">General</string>
184 <string name="preferences_system">Sistema</string>
185 <string name="preferences_graphics">Gráficos</string>
186 <string name="preferences_audio">Audio</string>
187 <string name="preferences_theme">Tema y color</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">Su ROM está encriptada</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor, siga las guías para redumpear <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">cartuchos de juegos</a> o <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">titulos instalados</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Por favor, compruebe que su archivo <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> está instalado, para que los juegos sean descifrados.]]></string>
193 <string name="loader_error_video_core">Ocurrió un error al inicializar el núcleo de video, posiblemente debido a una incompatibilidad con el driver seleccionado</string>
194 <string name="loader_error_video_core_description">Esto suele deberse a un driver de GPU incompatible. La instalación de un controlador de GPU personalizado puede resolver este problema.</string>
195 <string name="loader_error_invalid_format">No se pudo cargar la ROM</string>
196 <string name="loader_error_file_not_found">Archivo ROM no existe</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Salir de la emulación</string>
200 <string name="emulation_done">Hecho</string>
201 <string name="emulation_fps_counter">Contador de FPS</string>
202 <string name="emulation_toggle_controls">Alternar Controles</string>
203 <string name="emulation_rel_stick_center">Centro Relativo del Stick</string>
204 <string name="emulation_dpad_slide">Deslizamiento de la Cruceta</string>
205 <string name="emulation_haptics">Hápticos</string>
206 <string name="emulation_show_overlay">Mostrar pantalla</string>
207 <string name="emulation_toggle_all">Alternar Todo</string>
208 <string name="emulation_control_adjust">Ajustar pantalla</string>
209 <string name="emulation_control_scale">Escala</string>
210 <string name="emulation_control_opacity">Opacidad</string>
211 <string name="emulation_touch_overlay_reset">Reiniciar pantalla</string>
212 <string name="emulation_touch_overlay_edit">Editar pantalla</string>
213 <string name="emulation_pause">Pausar Emulación</string>
214 <string name="emulation_unpause">Reanudar Emulación</string>
215 <string name="emulation_input_overlay">Opciones de pantalla </string>
216 <string name="emulation_game_loading">Cargando juego...</string>
217
218 <string name="load_settings">Cargando configuración...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Software del teclado</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Abortar</string>
225 <string name="continue_button">Continuar</string>
226 <string name="system_archive_not_found">Archivo del sistema no encontrado</string>
227 <string name="system_archive_not_found_message">%s no se ha encontrado. Vacíe los archivos de su sistema.\nContinuar con la emulación puede provocar bloqueos y errores.</string>
228 <string name="system_archive_general">Un archivo del sistema</string>
229 <string name="save_load_error">Error de Guardado/Carga</string>
230 <string name="fatal_error">Error fatal</string>
231 <string name="fatal_error_message">Ocurrió un error fatal. Consulte el registro para obtener más detalles.\nContinuar con la emulación puede provocar bloqueos y errores.</string>
232 <string name="performance_warning">¡Desactivar esta configuración reducirá significativamente el rendimiento de la emulación! Para obtener la mejor experiencia, se recomienda dejar esta configuración habilitada.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Japón</string>
236 <string name="region_usa">EEUU</string>
237 <string name="region_europe">Europa</string>
238 <string name="region_australia">Australia</string>
239 <string name="region_china">China</string>
240 <string name="region_korea">Corea</string>
241 <string name="region_taiwan">Taiwán</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Japonés (日本語)</string>
245 <string name="language_english">Inglés (English)</string>
246 <string name="language_french">Francés (Français)</string>
247 <string name="langauge_german">Alemán (deutsch)</string>
248 <string name="language_italian">Italiano (Italiano)</string>
249 <string name="language_spanish">Español (Español)</string>
250 <string name="language_chinese">Chino (简体中文)</string>
251 <string name="language_korean">Coreano (한국어)</string>
252 <string name="language_dutch">Holandés (nederlands)</string>
253 <string name="language_portuguese">Portugués (Português)</string>
254 <string name="language_russian">Ruso (Русский)</string>
255 <string name="language_taiwanese">Taiwanés (台湾)</string>
256 <string name="language_british_english">Inglés británico</string>
257 <string name="language_canadian_french">Francés Canadiense (Français canadien)</string>
258 <string name="language_latin_american_spanish">Español Latinoamericano (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">Chino Simplificado (简体中文)</string>
260 <string name="language_traditional_chinese">Chino tradicional (正體中文)</string>
261 <string name="language_brazilian_portuguese">Portugués Brasileño (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">Ninguno</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Normal</string>
269 <string name="renderer_accuracy_high">Alto</string>
270 <string name="renderer_accuracy_extreme">Extremo (Lento)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">x1 (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Lento)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Lento)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Lento)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Inmediata (Desactivado)</string>
282 <string name="renderer_vsync_mailbox">Mailbox</string>
283 <string name="renderer_vsync_fifo">FIFO (Activado)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO Relajado</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Vecino más próximo</string>
288 <string name="scaling_filter_bilinear">Bilineal</string>
289 <string name="scaling_filter_bicubic">Bicúbico</string>
290 <string name="scaling_filter_gaussian">Gaussiano</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolución</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Ninguno</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">Predeterminado (16:9)</string>
301 <string name="ratio_force_four_three">Forzar 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Forzar 21:9</string>
303 <string name="ratio_force_sixteen_ten">Forzar 16:10</string>
304 <string name="ratio_stretch">Ajustar a la ventana</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Preciso</string>
308 <string name="cpu_accuracy_unsafe">Impreciso</string>
309 <string name="cpu_accuracy_paranoid">Paranoico (Lento)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">Cruceta</string>
313 <string name="gamepad_left_stick">Palanca izquierda</string>
314 <string name="gamepad_right_stick">Palanca derecha</string>
315 <string name="gamepad_home">Home</string>
316 <string name="gamepad_screenshot">Captura de pantalla</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">Preparando shaders</string>
320 <string name="building_shaders">Construyendo shaders</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Cambiar Tema</string>
324 <string name="theme_default">Predeterminado</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Cambiar modo del tema</string>
329 <string name="theme_mode_follow_system">Igual al sistema</string>
330 <string name="theme_mode_light">Claro</string>
331 <string name="theme_mode_dark">Oscuro</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Usar Fondos Negros</string>
335 <string name="use_black_backgrounds_description">Cuando utilice el modo oscuro, aplique fondos negros.</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 000000000..14a9b2d5c
--- /dev/null
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Ce logiciel exécutera des jeux pour la console de jeu Nintendo Switch. Aucun jeux ou clés n\'est inclus.&lt;br /&gt;&lt;br /&gt;Avant de commencer, veuillez localiser votre fichier <![CDATA[<b> prod.keys </b>]]> sur le stockage de votre appareil.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">En savoir plus</a>]]></string>
5 <string name="emulation_notification_channel_name">L\'émulation est active</string>
6 <string name="emulation_notification_channel_description">Affiche une notification persistante lorsque l\'émulation est en cours d\'exécution.</string>
7 <string name="emulation_notification_running">yuzu est en cours d\'exécution</string>
8 <string name="notice_notification_channel_name">Avis et erreurs</string>
9 <string name="notice_notification_channel_description">Affiche des notifications en cas de problème.</string>
10 <string name="notification_permission_not_granted">Permission de notification non accordée !</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Bienvenue !</string>
14 <string name="welcome_description">Apprenez à configurer &lt;b>yuzu&lt;/b> et passez à l\'émulation.</string>
15 <string name="get_started">Commencer</string>
16 <string name="keys">Clés</string>
17 <string name="keys_description">Sélectionnez votre fichier &lt;b>prod.keys&lt;/b> avec le bouton ci-dessous.</string>
18 <string name="select_keys">Sélectionner les clés</string>
19 <string name="games">Jeux</string>
20 <string name="games_description">Sélectionnez votre dossier &lt;b>de Jeux&lt;/b> avec le bouton ci-dessous.</string>
21 <string name="done">Terminé</string>
22 <string name="done_description">Vous êtes prêt.\nProfitez de vos jeux !</string>
23 <string name="text_continue">Continuer</string>
24 <string name="next">Suivant</string>
25 <string name="back">Retour</string>
26 <string name="add_games">Ajouter des jeux</string>
27 <string name="add_games_description">Sélectionner votre dossier de jeux</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Jeux</string>
31 <string name="home_search">Rechercher</string>
32 <string name="home_settings">Paramètres</string>
33 <string name="empty_gamelist">Aucun fichier n\'a été trouvé ou aucun répertoire de jeu n\'a encore été sélectionné.</string>
34 <string name="search_and_filter_games">Rechercher et filtrer les jeux</string>
35 <string name="select_games_folder">Sélectionner le dossier de jeux</string>
36 <string name="select_games_folder_description">Permet à yuzu de remplir la liste des jeux</string>
37 <string name="add_games_warning">Ne pas sélectionner le dossier des jeux ?</string>
38 <string name="add_games_warning_description">Les jeux ne seront pas affichés dans la liste des jeux si aucun dossier n\'est sélectionné.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Rechercher des jeux</string>
41 <string name="games_dir_selected">Répertoire de jeux sélectionné</string>
42 <string name="install_prod_keys">Installer prod.keys</string>
43 <string name="install_prod_keys_description">Nécessaire pour décrypter les jeux commerciaux.</string>
44 <string name="install_prod_keys_warning">Sauter l\'ajout des clés ?</string>
45 <string name="install_prod_keys_warning_description">Des clés valides sont nécessaires pour émuler des jeux commerciaux. Seules les applications homebrew fonctionneront si vous continuez.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Notifications</string>
48 <string name="notifications_description">Accordez l\'autorisation de notification avec le bouton ci-dessous.</string>
49 <string name="give_permission">Donner la permission</string>
50 <string name="notification_warning">Ne pas accorder la permission de notification ?</string>
51 <string name="notification_warning_description">yuzu ne pourra pas vous communiquer d\'informations importantes.</string>
52 <string name="permission_denied">Permission refusée</string>
53 <string name="permission_denied_description">Vous avez refusé cette permission trop de fois et vous devez maintenant l\'accorder manuellement dans les paramètres système.</string>
54 <string name="about">À propos</string>
55 <string name="about_description">Numéro de build, crédits et plus encore</string>
56 <string name="warning_help">Aide</string>
57 <string name="warning_skip">Sauter</string>
58 <string name="warning_cancel">Annuler</string>
59 <string name="install_amiibo_keys">Installer les clés Amiibo</string>
60 <string name="install_amiibo_keys_description">Nécessaire pour utiliser les Amiibo en jeu</string>
61 <string name="invalid_keys_file">Fichier de clés sélectionné invalide</string>
62 <string name="install_keys_success">Clés installées avec succès</string>
63 <string name="reading_keys_failure">Erreur lors de la lecture des clés de chiffrement</string>
64 <string name="invalid_keys_error">Clés de chiffrement invalides</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">Le fichier sélectionné est incorrect ou corrompu. Veuillez dumper à nouveau vos clés.</string>
67 <string name="install_gpu_driver">Installer le pilote du GPU</string>
68 <string name="install_gpu_driver_description">Installez des pilotes alternatifs pour des performances ou une précision potentiellement meilleures</string>
69 <string name="advanced_settings">Paramètres avancés</string>
70 <string name="settings_description">Configurer les paramètres de l\'émulateur</string>
71 <string name="search_recently_played">Joué récemment</string>
72 <string name="search_recently_added">Ajouté récemment</string>
73 <string name="search_retail">Commercial</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Ouvrir le dossier de yuzu</string>
76 <string name="open_user_folder_description">Gérer les fichiers internes de yuzu</string>
77 <string name="theme_and_color_description">Modifier l\'apparence de l\'application</string>
78 <string name="no_file_manager">Aucun gestionnaire de fichiers trouvé</string>
79 <string name="notification_no_directory_link">Impossible d\'ouvrir le répertoire de yuzu</string>
80 <string name="notification_no_directory_link_description">Veuillez localiser manuellement le dossier utilisateur avec le panneau latéral du gestionnaire de fichiers.</string>
81 <string name="manage_save_data">Gérer les données de sauvegarde</string>
82 <string name="manage_save_data_description">Données de sauvegarde trouvées. Veuillez sélectionner une option ci-dessous.</string>
83 <string name="import_export_saves_description">Importer ou exporter des fichiers de sauvegarde</string>
84 <string name="import_export_saves_no_profile">Aucune données de sauvegarde trouvées. Veuillez lancer un jeu et réessayer.</string>
85 <string name="save_file_imported_success">Importé avec succès</string>
86 <string name="save_file_invalid_zip_structure">Structure de répertoire de sauvegarde non valide</string>
87 <string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string>
88 <string name="import_saves">Importer</string>
89 <string name="export_saves">Exporter</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia n\'est pas réel</string>
93 <string name="copied_to_clipboard">Copié dans le presse-papier</string>
94 <string name="about_app_description">Un émulateur Switch open source</string>
95 <string name="contributors">Contributeurs</string>
96 <string name="contributors_description">Fait avec \u2764 de l\'équipe yuzu</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Build</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Early Access</string>
105 <string name="get_early_access">Obtenir l\'Early Access</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Fonctionnalités de pointe, accès anticipé aux mises à jour, et plus encore</string>
108 <string name="early_access_benefits">Avantages de l\'Early Access</string>
109 <string name="cutting_edge_features">Fonctionnalités de pointe</string>
110 <string name="early_access_updates">Accès anticipé aux mises à jour</string>
111 <string name="no_manual_installation">Pas d\'installation manuelle</string>
112 <string name="prioritized_support">Assistance prioritaire</string>
113 <string name="helping_game_preservation">Contribuer à la préservation des jeux</string>
114 <string name="our_eternal_gratitude">Notre gratitude éternelle</string>
115 <string name="are_you_interested">Es tu intéressé ?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Activer la vitesse limite</string>
119 <string name="frame_limit_enable_description">Lorsqu\'elle est activée, la vitesse d\'émulation sera limitée à un pourcentage spécifié de la vitesse normale.</string>
120 <string name="frame_limit_slider">Limite en pourcentage de vitesse</string>
121 <string name="frame_limit_slider_description">Spécifie le pourcentage pour limiter la vitesse d\'émulation. Avec la valeur par défaut de 100%, l\'émulation sera limitée à la vitesse normale. Des valeurs supérieures ou inférieures augmenteront ou diminueront la limite de vitesse.</string>
122 <string name="cpu_accuracy">Précision du CPU</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Mode TV</string>
126 <string name="use_docked_mode_description">Émuler en mode TV augmente la résolution au détriment des performances.</string>
127 <string name="emulated_region">Région émulée</string>
128 <string name="emulated_language">Langue émulée</string>
129 <string name="select_rtc_date">Sélectionner la date RTC</string>
130 <string name="select_rtc_time">Sélectionner l\'heure RTC</string>
131 <string name="use_custom_rtc">Activer l\'horloge RTC personnalisée</string>
132 <string name="use_custom_rtc_description">Ce paramètre vous permet de définir une horloge en temps réel personnalisée distincte de l\'heure actuelle de votre système.</string>
133 <string name="set_custom_rtc">Définir l\'horloge RTC personnalisée</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">Niveau de précision</string>
138 <string name="renderer_resolution">Résolution</string>
139 <string name="renderer_vsync">Mode VSync</string>
140 <string name="renderer_aspect_ratio">Format</string>
141 <string name="renderer_scaling_filter">Filtre de fenêtre adaptatif</string>
142 <string name="renderer_anti_aliasing">Méthode d\'anticrénelage :</string>
143 <string name="renderer_force_max_clock">Forcer la fréquence d\'horloge maximale (Adreno uniquement)</string>
144 <string name="renderer_force_max_clock_description">Force le GPU à fonctionner au maximum d\'horloges possibles (les contraintes thermiques seront toujours appliquées).</string>
145 <string name="renderer_asynchronous_shaders">Utiliser les shaders asynchrones</string>
146 <string name="renderer_asynchronous_shaders_description">Compile les shaders de manière asynchrone, ce qui réduira les saccades mais peut entraîner des problèmes visuels.</string>
147 <string name="renderer_debug">Activer le débogage des graphismes</string>
148 <string name="renderer_debug_description">Lorsque cette case est cochée, l\'API graphique entre dans un mode de débogage plus lent.</string>
149 <string name="use_disk_shader_cache">Utiliser les shader cache de disque</string>
150 <string name="use_disk_shader_cache_description">Réduire les saccades en stockant et en chargeant les shaders générés sur le disque.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Volume</string>
154 <string name="audio_volume_description">Spécifie le volume de la sortie audio.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">Défaut</string>
158 <string name="ini_saved">Paramètres enregistrés</string>
159 <string name="gameid_saved">Paramètres enregistrés pour %1$s</string>
160 <string name="error_saving">Erreur lors de l\'enregistrement de %1$s.ini: %2$s</string>
161 <string name="loading">Chargement...</string>
162 <string name="reset_setting_confirmation">Voulez-vous réinitialiser ce paramètre à sa valeur par défaut ?</string>
163 <string name="reset_to_default">Réinitialiser par défaut</string>
164 <string name="reset_all_settings">Réinitialiser tous les réglages ?</string>
165 <string name="reset_all_settings_description">Tous les paramètres avancés seront réinitialisés à leur configuration par défaut. Ça ne peut pas être annulé.</string>
166 <string name="settings_reset">Paramètres réinitialisés</string>
167 <string name="close">Fermer</string>
168 <string name="learn_more">Plus d\'informations</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Sélectionner le pilote du GPU</string>
172 <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
173 <string name="select_gpu_driver_install">Installer</string>
174 <string name="select_gpu_driver_default">Défaut</string>
175 <string name="select_gpu_driver_install_success">%s Installé</string>
176 <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
177 <string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string>
178 <string name="system_gpu_driver">Pilote du GPU du système</string>
179 <string name="installing_driver">Installation du pilote...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Paramètres</string>
183 <string name="preferences_general">Général</string>
184 <string name="preferences_system">Système</string>
185 <string name="preferences_graphics">Vidéo</string>
186 <string name="preferences_audio">Audio</string>
187 <string name="preferences_theme">Thème et couleur</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">Votre ROM est cryptée</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Veuillez suivre les guides pour redumper vos <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">cartouches de jeu</a> ou <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">titres installés</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Veuillez vous assurer que votre fichier <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> est installé pour que les jeux puissent être déchiffrés.]]></string>
193 <string name="loader_error_video_core">Une erreur s\'est produite lors de l\'initialisation du noyau vidéo</string>
194 <string name="loader_error_video_core_description">Cela est généralement dû à un pilote du GPU incompatible. L\'installation d\'un pilote du GPU personnalisé peut résoudre ce problème.</string>
195 <string name="loader_error_invalid_format">Impossible de charger la ROM</string>
196 <string name="loader_error_file_not_found">Le fichier ROM n\'existe pas</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Quitter l\'émulation</string>
200 <string name="emulation_done">Terminé</string>
201 <string name="emulation_fps_counter">Compteur FPS</string>
202 <string name="emulation_toggle_controls">Activer/Désactiver les contrôles</string>
203 <string name="emulation_rel_stick_center">Centre du stick relatif</string>
204 <string name="emulation_dpad_slide">Glissement du DPad</string>
205 <string name="emulation_haptics">Haptique</string>
206 <string name="emulation_show_overlay">Afficher l\'overlay</string>
207 <string name="emulation_toggle_all">Tout basculer</string>
208 <string name="emulation_control_adjust">Ajuster l\'overlay</string>
209 <string name="emulation_control_scale">Échelle</string>
210 <string name="emulation_control_opacity">Opacité</string>
211 <string name="emulation_touch_overlay_reset">Réinitialiser l\'overlay</string>
212 <string name="emulation_touch_overlay_edit">Modifier l\'overlay</string>
213 <string name="emulation_pause">Mettre en pause l\'émulation</string>
214 <string name="emulation_unpause">Reprendre l\'émulation</string>
215 <string name="emulation_input_overlay">Options de l\'overlay</string>
216 <string name="emulation_game_loading">Chargement du jeu...</string>
217
218 <string name="load_settings">Chargement des paramètres…</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Clavier virtuel</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Abandonner</string>
225 <string name="continue_button">Continuer</string>
226 <string name="system_archive_not_found">Archive système introuvable</string>
227 <string name="system_archive_not_found_message">%s est manquant. Veuillez dumper vos archives système.\nContinuer peut entraîner des plantages et des bogues.</string>
228 <string name="system_archive_general">Une archive système</string>
229 <string name="save_load_error">Erreur de sauvegarde/chargement</string>
230 <string name="fatal_error">Erreur fatale</string>
231 <string name="fatal_error_message">Une erreur fatale s\'est produite. Consultez les logs pour plus de détails.\nContinuer l\'émulation peut entraîner des plantages et des bogues.</string>
232 <string name="performance_warning">La désactivation de ce paramètre réduira considérablement les performances d\'émulation ! Pour une expérience optimale, il est recommandé de laisser ce paramètre activé.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Japon</string>
236 <string name="region_usa">É.-U.A.</string>
237 <string name="region_europe">Europe</string>
238 <string name="region_australia">Australie</string>
239 <string name="region_china">Chine</string>
240 <string name="region_korea">Corée</string>
241 <string name="region_taiwan">Taïwan</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Japonais (日本語)</string>
245 <string name="language_english">Anglais</string>
246 <string name="language_french">Français (Français)</string>
247 <string name="langauge_german">Allemand (Deutsch)</string>
248 <string name="language_italian">Italien (Italiano)</string>
249 <string name="language_spanish">Espagnol (Español)</string>
250 <string name="language_chinese">Chinois (简体中文)</string>
251 <string name="language_korean">Coréen (한국어)</string>
252 <string name="language_dutch">Néerlandais (Nederlands)</string>
253 <string name="language_portuguese">Portugais (Português)</string>
254 <string name="language_russian">Russe (Русский)</string>
255 <string name="language_taiwanese">Taïwanais (台湾)</string>
256 <string name="language_british_english">Anglais Britannique</string>
257 <string name="language_canadian_french">Français canadien (Français canadien)</string>
258 <string name="language_latin_american_spanish">Espagnol latino-américain (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">Chinois simplifié (简体中文)</string>
260 <string name="language_traditional_chinese">Chinois Traditionnel (正體中文)</string>
261 <string name="language_brazilian_portuguese">Portugais brésilien (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">Aucune</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Normal</string>
269 <string name="renderer_accuracy_high">Haut</string>
270 <string name="renderer_accuracy_extreme">Extrême (Lent)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Lent)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Lent)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Lent)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Immédiat (Désactivé)</string>
282 <string name="renderer_vsync_mailbox">Mailbox</string>
283 <string name="renderer_vsync_fifo">FIFO (Activé)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO souple</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Plus proche voisin</string>
288 <string name="scaling_filter_bilinear">Bilinéaire</string>
289 <string name="scaling_filter_bicubic">Bicubique</string>
290 <string name="scaling_filter_gaussian">Gaussien</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Aucune</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">Par défaut (16:9)</string>
301 <string name="ratio_force_four_three">Forcer le 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Forcer le 21:9</string>
303 <string name="ratio_force_sixteen_ten">Forcer le 16:10</string>
304 <string name="ratio_stretch">Étirer à la fenêtre</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Précis</string>
308 <string name="cpu_accuracy_unsafe">Risqué</string>
309 <string name="cpu_accuracy_paranoid">Paranoïaque (Lent)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">Pavé directionnel</string>
313 <string name="gamepad_left_stick">Stick Gauche</string>
314 <string name="gamepad_right_stick">Stick Droit</string>
315 <string name="gamepad_home">Home</string>
316 <string name="gamepad_screenshot">Capture d\'écran</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">Préparation des shaders</string>
320 <string name="building_shaders">Compilation des shaders</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Changer le thème de l\'application</string>
324 <string name="theme_default">Défaut</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Changer le mode de thème</string>
329 <string name="theme_mode_follow_system">Automatique</string>
330 <string name="theme_mode_light">Lumineux</string>
331 <string name="theme_mode_dark">Sombre</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Utiliser des arrière-plans noirs</string>
335 <string name="use_black_backgrounds_description">Lorsque vous utilisez le thème sombre, appliquer des arrière-plans noirs.</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
new file mode 100644
index 000000000..47a4cfa31
--- /dev/null
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Questo software permette di giocare ai giochi della console Nintendo Switch. Nessun gioco o chiave è inclusa.&lt;br /&gt;&lt;br /&gt;Prima di iniziare, perfavore individua il file <![CDATA[<b>prod.keys </b>]]> nella memoria del tuo dispositivo.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Scopri di più</a>]]></string>
5 <string name="emulation_notification_channel_name">L\'emulatore è attivo</string>
6 <string name="emulation_notification_channel_description">Mostra una notifica persistente quando l\'emulatore è in esecuzione.</string>
7 <string name="emulation_notification_running">yuzu è in esecuzione</string>
8 <string name="notice_notification_channel_name">Avvisi ed errori</string>
9 <string name="notice_notification_channel_description">Mostra le notifiche quando qualcosa va storto.</string>
10 <string name="notification_permission_not_granted">Autorizzazione di notifica non concessa!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Benvenuto!</string>
14 <string name="welcome_description">Scopri come configurare &lt;b>yuzu&lt;/b> e passare all\'emulazione.</string>
15 <string name="get_started">Iniziare</string>
16 <string name="keys">Pulsanti</string>
17 <string name="keys_description">Seleziona il tuo file &lt;b>prod.keys&lt;/b> con il pulsante in basso.</string>
18 <string name="select_keys">Selezione Pulsanti</string>
19 <string name="games">Giochi</string>
20 <string name="games_description">Seleziona la cartella &lt;b>Games&lt;/b> con il pulsante in basso.</string>
21 <string name="done">Fatto</string>
22 <string name="done_description">È tutto pronto.\nDivertiti a giocare!</string>
23 <string name="text_continue">Continua</string>
24 <string name="next">Successivo</string>
25 <string name="back">Indietro</string>
26 <string name="add_games">Aggiungi giochi</string>
27 <string name="add_games_description">Seleziona la cartella dei giochi</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Giochi</string>
31 <string name="home_search">Cerca</string>
32 <string name="home_settings">Impostazioni</string>
33 <string name="empty_gamelist">Non sono stati trovati file o non è stata ancora selezionata alcuna directory di gioco.</string>
34 <string name="search_and_filter_games">Cerca e filtra i giochi</string>
35 <string name="select_games_folder">Seleziona la cartella di gioco</string>
36 <string name="select_games_folder_description">Consente a yuzu di popolare l\'elenco dei giochi</string>
37 <string name="add_games_warning">Saltare la selezione della cartella dei giochi?</string>
38 <string name="add_games_warning_description">I giochi non saranno mostrati nella lista dei giochi se una cartella non è selezionata.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Cerca giochi</string>
41 <string name="games_dir_selected">Cartella dei giochi selezionata</string>
42 <string name="install_prod_keys">Installa prod.keys</string>
43 <string name="install_prod_keys_description">Necessario per decrittografare i giochi</string>
44 <string name="install_prod_keys_warning">Saltare l\'aggiunta delle chiavi?</string>
45 <string name="install_prod_keys_warning_description">Sono necessarie delle chiavi valide per emulare i giochi. Se continui, funzioneranno solo le app homebrew.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Notifiche</string>
48 <string name="notifications_description">Concedi l\'autorizzazione alle notifiche con il pulsante in basso.</string>
49 <string name="give_permission">Concedere l\'autorizzazione</string>
50 <string name="notification_warning">Saltare la concessione dell\'autorizzazione alle notifiche?</string>
51 <string name="notification_warning_description">yuzu non sarà in grado di notificarti informazioni importanti.</string>
52 <string name="permission_denied">Permesso negato</string>
53 <string name="permission_denied_description">Hai negato l\'autorizzazione troppe volte ed ora devi concederla manualmente nelle impostazioni di sistema.</string>
54 <string name="about">Informazioni</string>
55 <string name="about_description">Versione build, crediti ed altro</string>
56 <string name="warning_help">Aiuto</string>
57 <string name="warning_skip">Salta</string>
58 <string name="warning_cancel">Annulla</string>
59 <string name="install_amiibo_keys">Installa le chiavi degli Amiibo</string>
60 <string name="install_amiibo_keys_description">Necessario per usare gli Amiibo in gioco</string>
61 <string name="invalid_keys_file">Selezionate chiavi non valide</string>
62 <string name="install_keys_success">Chiavi installate correttamente</string>
63 <string name="reading_keys_failure">Errore durante la lettura delle chiavi di crittografia</string>
64 <string name="invalid_keys_error">Chiavi di crittografia non valide</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">Il file selezionato è incorretto o corrotto. Per favore riesegui il dump delle tue chiavi.</string>
67 <string name="install_gpu_driver">Installa i driver GPU</string>
68 <string name="install_gpu_driver_description">Installa driver alternativi per potenziali prestazioni migliori o accuratezza.</string>
69 <string name="advanced_settings">Impostazioni avanzate</string>
70 <string name="settings_description">Configura le impostazioni dell\'emulatore</string>
71 <string name="search_recently_played">Giocato recentemente</string>
72 <string name="search_recently_added">Aggiunto recentemente</string>
73 <string name="search_retail">Rivenditore</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Apri la cartella di yuzu</string>
76 <string name="open_user_folder_description">Gestisci i file interni di yuzu</string>
77 <string name="theme_and_color_description">Modifica l\'aspetto dell\'app</string>
78 <string name="no_file_manager">Nessun file manager trovato</string>
79 <string name="notification_no_directory_link">Impossibile aprire la cartella di yuzu</string>
80 <string name="notification_no_directory_link_description">Per favore individua la cartella dell\'utente manualmente con il pannello laterale del file manager.</string>
81 <string name="manage_save_data">Gestisci i salvataggi</string>
82 <string name="manage_save_data_description">Salvataggio non trovato. Seleziona un\'opzione di seguito.</string>
83 <string name="import_export_saves_description">Importa o esporta i salvataggi</string>
84 <string name="import_export_saves_no_profile">Nessun salvataggio trovato. Avvia un gioco e riprova.</string>
85 <string name="save_file_imported_success">Importato con successo</string>
86 <string name="save_file_invalid_zip_structure">La struttura della cartella dei salvataggi è invalida</string>
87 <string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string>
88 <string name="import_saves">Importa</string>
89 <string name="export_saves">Esporta</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia non è reale</string>
93 <string name="copied_to_clipboard">Copiato negli appunti</string>
94 <string name="about_app_description">Un emulatore della Switch open-source.</string>
95 <string name="contributors">Collaboratori</string>
96 <string name="contributors_description">Realizzato con \u2764 dal team yuzu</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Compilazione</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Accesso Anticipato</string>
105 <string name="get_early_access">Ottieni l\'accesso anticipato</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Funzionalità all\'avanguardia, aggiornamenti in anticipo e altro</string>
108 <string name="early_access_benefits">Vantaggi dell\'accesso anticipato</string>
109 <string name="cutting_edge_features">Funzionalità all\'avanguardia</string>
110 <string name="early_access_updates">Accesso anticipato agli aggiornamenti</string>
111 <string name="no_manual_installation">Non installare manualmente.</string>
112 <string name="prioritized_support">Supporto prioritario</string>
113 <string name="helping_game_preservation">Aiuta a preservare il gioco</string>
114 <string name="our_eternal_gratitude">La nostra gratitudine eterna</string>
115 <string name="are_you_interested">Sei interessato?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Abilita il limite di velocità</string>
119 <string name="frame_limit_enable_description">Quando abilitato, la velocità di emulazione verrà limitata a una specifica percentuale della velocità normale.</string>
120 <string name="frame_limit_slider">Limite velocità percentuale</string>
121 <string name="frame_limit_slider_description">Specifica la percentuale del limite della velocità di emulazione. Con quella preimpostata al 100% l\'emulazione verrà limitata alla velocità normale. Valori più alti o bassi aumenteranno o diminuiranno il limite di velocità.</string>
122 <string name="cpu_accuracy">Accuratezza della CPU</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Modalità docked</string>
126 <string name="use_docked_mode_description">Emula in modalità docked, questo aumenta la risoluzione a spese delle performance.</string>
127 <string name="emulated_region">Regione emulata</string>
128 <string name="emulated_language">Lingua emulata</string>
129 <string name="select_rtc_date">Seleziona la data dall\'orologio in tempo reale</string>
130 <string name="select_rtc_time">Seleziona il tempo dall\'orologio in tempo reale</string>
131 <string name="use_custom_rtc">Abilità l\'orologio in tempo reale personalizzato</string>
132 <string name="use_custom_rtc_description">Questa impostazione ti permette di impostare un orologio in tempo reale personalizzato separato da quello del tuo sistema corrente.</string>
133 <string name="set_custom_rtc">Imposta l\'orologio in tempo reale personalizzato</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">Livello di accuratezza</string>
138 <string name="renderer_resolution">Risoluzione</string>
139 <string name="renderer_vsync">Modalità VSync</string>
140 <string name="renderer_aspect_ratio">Rapporto d\'aspetto</string>
141 <string name="renderer_scaling_filter">Filtro di adattamento alla finestra</string>
142 <string name="renderer_anti_aliasing">Metodo di anti-aliasing</string>
143 <string name="renderer_force_max_clock">Forza clock massimi (solo Adreno)</string>
144 <string name="renderer_force_max_clock_description">Forza la GPU a girare col massimo clock possibile (i vincoli alla temperatura saranno comunque applicati)</string>
145 <string name="renderer_asynchronous_shaders">Usa shaders asincrone</string>
146 <string name="renderer_asynchronous_shaders_description">Compila le shaders asincronamente, questo riduce lo shutter ma potrebbe introdurre dei glitch. </string>
147 <string name="renderer_debug">Abilità il debug grafico</string>
148 <string name="renderer_debug_description">Quando l\'opzione è selezionata, l\'API grafica entra in una modalità di debug più lenta</string>
149 <string name="use_disk_shader_cache">Usa cache shader su disco</string>
150 <string name="use_disk_shader_cache_description">Riduce lo stuttering salvando e caricando le shader generate sul disco.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Volume</string>
154 <string name="audio_volume_description">Specifica il volume dell\'audio in uscita.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">Predefinito</string>
158 <string name="ini_saved">Impostazioni salvate</string>
159 <string name="gameid_saved">Impostazioni salvate per %1$s</string>
160 <string name="error_saving">Errore nel salvare %1$s.ini %2$s</string>
161 <string name="loading">Caricamento…</string>
162 <string name="reset_setting_confirmation">Vuoi ripristinare queste impostazioni al loro valore originale?</string>
163 <string name="reset_to_default">Riportare alle impostazioni originali</string>
164 <string name="reset_all_settings">Resettare tutte le impostazioni?</string>
165 <string name="reset_all_settings_description">Tutte le Impostazioni Avanzate saranno ripristinate a quelle originali. Questa operazione non è reversibile</string>
166 <string name="settings_reset">Reimposta le impostazioni</string>
167 <string name="close">Chiudi</string>
168 <string name="learn_more">Per saperne di più</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Seleziona il driver della GPU</string>
172 <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
173 <string name="select_gpu_driver_install">Installa</string>
174 <string name="select_gpu_driver_default">Predefinito</string>
175 <string name="select_gpu_driver_install_success">Installato%s</string>
176 <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
177 <string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string>
178 <string name="system_gpu_driver">Driver GPU del sistema</string>
179 <string name="installing_driver">Installando i driver...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Impostazioni</string>
183 <string name="preferences_general">Generali</string>
184 <string name="preferences_system">Sistema</string>
185 <string name="preferences_graphics">Grafica</string>
186 <string name="preferences_audio">Audio</string>
187 <string name="preferences_theme">Tema e colori</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">La tua ROM è criptata</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Per favore segui la guida per eseguire il dump della <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">cartuccia di gioco</a> o i <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">titoli installati</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Per favore assicurati che il file <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> sia installato in modo che i giochi possano essere decrittati.]]></string>
193 <string name="loader_error_video_core">È stato riscontrato un errore nell\'inizializzazione del core video</string>
194 <string name="loader_error_video_core_description">Questo è causato solitamente dal driver incompatibile di una GPU. L\'installazione di driver GPU personalizzati potrebbe risolvere questo problema.</string>
195 <string name="loader_error_invalid_format">Impossibile caricare la ROM</string>
196 <string name="loader_error_file_not_found">Il file della ROM non esiste</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Uscire dall\'emulazione</string>
200 <string name="emulation_done">Fatto</string>
201 <string name="emulation_fps_counter">Contatore degli FPS</string>
202 <string name="emulation_toggle_controls">Controlli a interruttore</string>
203 <string name="emulation_rel_stick_center">Centro relativo degli Stick</string>
204 <string name="emulation_dpad_slide">Slittamento del Pad Direzionale</string>
205 <string name="emulation_haptics">Aptico</string>
206 <string name="emulation_show_overlay">Mostra Overlay</string>
207 <string name="emulation_toggle_all">Attiva/disattiva tutto</string>
208 <string name="emulation_control_adjust">Aggiusta Overlay</string>
209 <string name="emulation_control_scale">Scala</string>
210 <string name="emulation_control_opacity">Opacità</string>
211 <string name="emulation_touch_overlay_reset">Reimposta Overlay</string>
212 <string name="emulation_touch_overlay_edit">Modifica Overlay</string>
213 <string name="emulation_pause">Metti in pausa l\'emulazione</string>
214 <string name="emulation_unpause">Riprendi Emulazione</string>
215 <string name="emulation_input_overlay">Impostazioni Overlay</string>
216 <string name="emulation_game_loading">Caricamento del gioco...</string>
217
218 <string name="load_settings">Caricamento delle impostazioni...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Tastiera software</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Interrompi</string>
225 <string name="continue_button">Continua</string>
226 <string name="system_archive_not_found">Archivio di sistema non trovato</string>
227 <string name="system_archive_not_found_message">%s è mancante. Per favore esegui il dump degli archivi del tuo sistema.\nContinuare ad emulare potrebbe portare bug o causare crash.</string>
228 <string name="system_archive_general">Un archivio di sistema</string>
229 <string name="save_load_error">Errore di salvataggio/caricamento</string>
230 <string name="fatal_error">Errore Fatale</string>
231 <string name="fatal_error_message">Un errore fatale è accaduto. Controlla i log per i dettagli.\nContinuare ad emulare potrebbe portare bug o causare crash.</string>
232 <string name="performance_warning">Disattivare questa impostazione può ridurre significativamente le performance di emulazione! Per una migliore esperienza, è consigliato lasciare questa impostazione attivata.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Giappone</string>
236 <string name="region_usa">USA</string>
237 <string name="region_europe">Europa</string>
238 <string name="region_australia">Australia</string>
239 <string name="region_china">Cina</string>
240 <string name="region_korea">Corea</string>
241 <string name="region_taiwan">Taiwan</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Giapponese (日本語)</string>
245 <string name="language_english">Inglese (English)</string>
246 <string name="language_french">Francese (Français)</string>
247 <string name="langauge_german">Tedesco (Deutsch)</string>
248 <string name="language_italian">Italiano (Italiano)</string>
249 <string name="language_spanish">Spagnolo (Español)</string>
250 <string name="language_chinese">Cinese (简体中文)</string>
251 <string name="language_korean">Coreano (한국어)</string>
252 <string name="language_dutch">Olandese (Nederlands)</string>
253 <string name="language_portuguese">Portoghese (Português)</string>
254 <string name="language_russian">Russo (Русский)</string>
255 <string name="language_taiwanese">Taiwanese (台湾)</string>
256 <string name="language_british_english">Inglese britannico</string>
257 <string name="language_canadian_french">Francese Canadese (Français canadien)</string>
258 <string name="language_latin_american_spanish">Spagnolo Latino Americano (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">Cinese Semplificato (简体中文)</string>
260 <string name="language_traditional_chinese">Cinese tradizionale (正體中文)</string>
261 <string name="language_brazilian_portuguese">Portoghese (Português)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">Nessuna</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Normale</string>
269 <string name="renderer_accuracy_high">Alta</string>
270 <string name="renderer_accuracy_extreme">Estrema (Lenta)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Slow)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Slow)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Slow)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Immediato (Off)</string>
282 <string name="renderer_vsync_mailbox">Cassella postale</string>
283 <string name="renderer_vsync_fifo">FIFO (On)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO Rilassato</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Nearest neighbor</string>
288 <string name="scaling_filter_bilinear">Bilineare</string>
289 <string name="scaling_filter_bicubic">Bicubico</string>
290 <string name="scaling_filter_gaussian">Gaussiano</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™️ Super Resolution</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Nessuna</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">Predefinito (16:9)</string>
301 <string name="ratio_force_four_three">Forza 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Forza 21:9</string>
303 <string name="ratio_force_sixteen_ten">Forza 16:10</string>
304 <string name="ratio_stretch">Allunga a finestra</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Accurata</string>
308 <string name="cpu_accuracy_unsafe">Non sicura</string>
309 <string name="cpu_accuracy_paranoid">Paranoico (Lento)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">D-Pad</string>
313 <string name="gamepad_left_stick">Levetta sinistra</string>
314 <string name="gamepad_right_stick">Levetta destra</string>
315 <string name="gamepad_home">Home</string>
316 <string name="gamepad_screenshot">Screenshot</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">Preparazione degli shaders</string>
320 <string name="building_shaders">Costruendo gli shaders</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Cambia il tema dell\'app</string>
324 <string name="theme_default">Predefinito</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Cambia la modalità del tema</string>
329 <string name="theme_mode_follow_system">Segue il Sistema</string>
330 <string name="theme_mode_light">Chiaro</string>
331 <string name="theme_mode_dark">Scuro</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Usa sfondi neri</string>
335 <string name="use_black_backgrounds_description">Quando utilizzi il tema scuro, applica sfondi neri.</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
new file mode 100644
index 000000000..46eda9ef7
--- /dev/null
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -0,0 +1,335 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">このソフトウェアは、Nintendo Switch用のゲームを実行します。 ゲームソフトやキーは含まれません。&lt;br /&gt;&lt;br /&gt;事前に、 <![CDATA[<b> prod.keys </b>]]> ファイルをデバイスのストレージに配置しておいてください。&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">詳細</a>]]></string>
5 <string name="emulation_notification_channel_name">エミュレーションが有効です</string>
6 <string name="emulation_notification_channel_description">エミュレーションの実行中に常設通知を表示します。</string>
7 <string name="emulation_notification_running">yuzu は実行中です</string>
8 <string name="notice_notification_channel_description">問題が発生したときに通知を表示します。</string>
9 <string name="notification_permission_not_granted">通知が許可されていません!</string>
10
11 <!-- Setup strings -->
12 <string name="welcome">ようこそ!</string>
13 <string name="welcome_description">&lt;b>yuzu&lt;/b> のセットアップ方法を学び、エミュレーションに飛び込みましょう。</string>
14 <string name="get_started">はじめる</string>
15 <string name="keys">キー</string>
16 <string name="keys_description">下のボタンから &lt;b>prod.keys&lt;/b> ファイルを選択してください。</string>
17 <string name="select_keys">キーを選択</string>
18 <string name="games">ゲーム</string>
19 <string name="games_description">下のボタンから&lt;b>ゲーム&lt;/b>があるフォルダを選択してください。</string>
20 <string name="done">完了</string>
21 <string name="done_description">準備が完了しました。\nゲームをお楽しみください!</string>
22 <string name="text_continue">続行</string>
23 <string name="next">次へ</string>
24 <string name="back">戻る</string>
25 <string name="add_games">ゲームを追加</string>
26 <string name="add_games_description">ゲームフォルダを選択</string>
27
28 <!-- Home strings -->
29 <string name="home_games">ゲーム</string>
30 <string name="home_search">検索</string>
31 <string name="home_settings">設定</string>
32 <string name="empty_gamelist">ファイルが見つからないか、ゲームディレクトリがまだ選択されていません。</string>
33 <string name="search_and_filter_games">ゲームの検索と絞り込み</string>
34 <string name="select_games_folder">ゲームフォルダを選択</string>
35 <string name="select_games_folder_description">yuzu がゲームリストに追加できるようにします</string>
36 <string name="add_games_warning">ゲームフォルダの選択をスキップしますか?</string>
37 <string name="add_games_warning_description">フォルダを選択しない場合、ゲームはゲームリストに表示されません。</string>
38 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
39 <string name="home_search_games">ゲームを検索</string>
40 <string name="games_dir_selected">ゲームディレクトリが選択されました</string>
41 <string name="install_prod_keys">prod.keys をインストール</string>
42 <string name="install_prod_keys_description">ゲームの復号化に必要</string>
43 <string name="install_prod_keys_warning">キーの追加をスキップしますか?</string>
44 <string name="install_prod_keys_warning_description">製品版ゲームのエミュレーションには、有効なキーが必要です。続行すると自作アプリしか機能しません。</string>
45 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
46 <string name="notifications">通知</string>
47 <string name="notifications_description">下のボタンで通知の権限を許可してください。</string>
48 <string name="give_permission">許可</string>
49 <string name="notification_warning">通知の許可をスキップしますか?</string>
50 <string name="notification_warning_description">yuzuは重要なお知らせを通知できません。</string>
51 <string name="permission_denied">権限が拒否されました</string>
52 <string name="permission_denied_description">この権限を複数回拒否したため、システム設定で手動で許可する必要があります。</string>
53 <string name="about">情報</string>
54 <string name="about_description">ビルドバージョン、クレジットなど</string>
55 <string name="warning_help">ヘルプ</string>
56 <string name="warning_skip">スキップ</string>
57 <string name="warning_cancel">キャンセル</string>
58 <string name="install_amiibo_keys">Amiibo キーをインストール</string>
59 <string name="install_amiibo_keys_description">ゲーム内での Amiibo の使用に必要</string>
60 <string name="invalid_keys_file">無効なキーファイルが選択されました</string>
61 <string name="install_keys_success">正常にインストールされました</string>
62 <string name="reading_keys_failure">暗号化キーの読み取りエラー</string>
63 <string name="invalid_keys_error">暗号化キーが無効です</string>
64 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
65 <string name="install_keys_failure_description">選択されたファイルが不正または破損しています。キーを再ダンプしてください。</string>
66 <string name="install_gpu_driver">GPUドライバーをインストール</string>
67 <string name="install_gpu_driver_description">代替ドライバーをインストールしてパフォーマンスや精度を向上させます</string>
68 <string name="advanced_settings">高度な設定</string>
69 <string name="settings_description">エミュレーターの設定を構成します</string>
70 <string name="search_recently_played">最近プレイした</string>
71 <string name="search_recently_added">最近追加された</string>
72 <string name="search_retail">製品版</string>
73 <string name="search_homebrew">自作</string>
74 <string name="open_user_folder">yuzu フォルダを開く</string>
75 <string name="open_user_folder_description">yuzu内部のファイルを管理します</string>
76 <string name="theme_and_color_description">アプリの見た目を変更</string>
77 <string name="no_file_manager">ファイルマネージャーが見つかりませんでした</string>
78 <string name="notification_no_directory_link">yuzuのディレクトリを開けません</string>
79 <string name="notification_no_directory_link_description">ファイルマネージャのサイドパネルでユーザーフォルダを手動で探してください。</string>
80 <string name="manage_save_data">セーブデータを管理</string>
81 <string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string>
82 <string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string>
83 <string name="import_export_saves_no_profile">セーブデータがありません。ゲームを起動してから再度お試しください。</string>
84 <string name="save_file_imported_success">インポートが完了しました</string>
85 <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string>
86 <string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string>
87 <string name="import_saves">インポート</string>
88 <string name="export_saves">エクスポート</string>
89
90 <!-- About screen strings -->
91 <string name="gaia_is_not_real">ガイアは実在しない</string>
92 <string name="copied_to_clipboard">クリップボードにコピーしました</string>
93 <string name="about_app_description">オープンソースのSwitchエミュレータ</string>
94 <string name="contributors">貢献者</string>
95 <string name="contributors_description">yuzuチームの\u2764で作られた</string>
96 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
97 <string name="build">ビルド</string>
98 <string name="support_link">https://discord.gg/u77vRWY</string>
99 <string name="website_link">https://yuzu-emu.org/</string>
100 <string name="github_link">https://github.com/yuzu-emu</string>
101
102 <!-- Early access upgrade strings -->
103 <string name="early_access">早期アクセス</string>
104 <string name="get_early_access">早期アクセスを手に入れる</string>
105 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
106 <string name="get_early_access_description">最先端の機能、アップデートの早期アクセスなど</string>
107 <string name="early_access_benefits">早期アクセスのメリット</string>
108 <string name="cutting_edge_features">最先端の機能</string>
109 <string name="early_access_updates">アップデートの早期アクセス</string>
110 <string name="no_manual_installation">手動インストールが不要</string>
111 <string name="prioritized_support">優先的なサポート</string>
112 <string name="helping_game_preservation">ゲームの保存に貢献</string>
113 <string name="our_eternal_gratitude">私たちの永遠の感謝</string>
114 <string name="are_you_interested">興味がありますか?</string>
115
116 <!-- General settings strings -->
117 <string name="frame_limit_enable">速度制限を有効化</string>
118 <string name="frame_limit_enable_description">有効にすると、エミュレーション速度が任意の割合に制限されます。</string>
119 <string name="frame_limit_slider">エミュレーション速度の制限</string>
120 <string name="frame_limit_slider_description">エミュレーション速度を制限する割合を指定します。デフォルトの100%では、エミュレーションは通常の速度に制限されます。値が高いまたは低いほど、速度制限が増加または減少します。</string>
121 <string name="cpu_accuracy">CPU精度</string>
122
123 <!-- System settings strings -->
124 <string name="use_docked_mode">TVモード</string>
125 <string name="use_docked_mode_description">TVモードでエミュレートします。パフォーマンスが犠牲になりますが、解像度が向上します。</string>
126 <string name="emulated_region">地域</string>
127 <string name="emulated_language">言語</string>
128 <string name="select_rtc_date">RTCの日付を選択</string>
129 <string name="select_rtc_time">RTCの時刻を選択</string>
130 <string name="use_custom_rtc">カスタムRTC</string>
131 <string name="use_custom_rtc_description">現在のシステム時間とは別にカスタムのリアルタイムクロックを設定できます。</string>
132 <string name="set_custom_rtc">カスタムRTCを設定</string>
133
134 <!-- Graphics settings strings -->
135 <string name="renderer_api">API</string>
136 <string name="renderer_accuracy">精度</string>
137 <string name="renderer_resolution">解像度</string>
138 <string name="renderer_vsync">垂直同期モード</string>
139 <string name="renderer_aspect_ratio">アスペクト比</string>
140 <string name="renderer_scaling_filter">ウィンドウ適応フィルター</string>
141 <string name="renderer_anti_aliasing">アンチエイリアス方式</string>
142 <string name="renderer_force_max_clock">最大クロックを強制 (Adrenoのみ)</string>
143 <string name="renderer_force_max_clock_description">GPUを可能な限り最大クロックで動作させます (過熱制限は引き続き適用されます)。</string>
144 <string name="renderer_asynchronous_shaders">非同期シェーダー</string>
145 <string name="renderer_asynchronous_shaders_description">シェーダーを非同期でコンパイルします。コマ落ちが軽減されますが、不具合が発生する可能性があります。</string>
146 <string name="renderer_debug">グラフィックデバッグ</string>
147 <string name="renderer_debug_description">オンにすると、グラフィックAPI は低速のデバッグモードに入ります。</string>
148 <string name="use_disk_shader_cache">シェーダーキャッシュを使用</string>
149 <string name="use_disk_shader_cache_description">生成したシェーダーをディスクに保存して読み込むことで、コマ落ちを軽減します。</string>
150
151 <!-- Audio settings strings -->
152 <string name="audio_volume">音量</string>
153 <string name="audio_volume_description">オーディオ出力の音量を指定します</string>
154
155 <!-- Miscellaneous -->
156 <string name="slider_default">デフォルト</string>
157 <string name="ini_saved">設定を保存しました</string>
158 <string name="gameid_saved">%1$sの設定を保存しました</string>
159 <string name="error_saving">%1$s.ini の保存エラー: %2$s</string>
160 <string name="loading">読み込み中…</string>
161 <string name="reset_setting_confirmation">この設定を初期値にリセットしますか?</string>
162 <string name="reset_to_default">初期設定に戻す</string>
163 <string name="reset_all_settings">すべての設定をリセットしますか?</string>
164 <string name="reset_all_settings_description">すべての詳細設定が初期設定に戻されます。この操作は元に戻せません。</string>
165 <string name="settings_reset">設定をリセットしました</string>
166 <string name="close">閉じる</string>
167 <string name="learn_more">詳細情報</string>
168
169 <!-- GPU driver installation -->
170 <string name="select_gpu_driver">GPUドライバを選択</string>
171 <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
172 <string name="select_gpu_driver_install">インストール</string>
173 <string name="select_gpu_driver_default">デフォルト</string>
174 <string name="select_gpu_driver_install_success">%s をインストールしました</string>
175 <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
176 <string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
177 <string name="system_gpu_driver">システムのGPUドライバ</string>
178 <string name="installing_driver">インストール中…</string>
179
180 <!-- Preferences Screen -->
181 <string name="preferences_settings">設定</string>
182 <string name="preferences_general">全般</string>
183 <string name="preferences_system">システム</string>
184 <string name="preferences_graphics">グラフィック</string>
185 <string name="preferences_audio">サウンド</string>
186 <string name="preferences_theme">テーマと色</string>
187
188 <!-- ROM loading errors -->
189 <string name="loader_error_encrypted">ROMが暗号化されています</string>
190 <string name="loader_error_encrypted_roms_description"><![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">ゲームカートリッジ</a>や<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">インストール済みのタイトル</a>を再度ダンプするためのガイドに従ってください。]]></string>
191 <string name="loader_error_encrypted_keys_description"><![CDATA[ゲームを復号化するために <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> ファイルがインストールされていることを確認してください。]]></string>
192 <string name="loader_error_video_core">ビデオコアの初期化中にエラーが発生しました</string>
193 <string name="loader_error_video_core_description">これは通常、互換性のないGPUドライバーが原因で発生します。 カスタムGPUドライバーをインストールすると、問題が解決する可能性があります。</string>
194 <string name="loader_error_invalid_format">ROMの読み込みに失敗しました</string>
195 <string name="loader_error_file_not_found">ROMファイルが存在しません</string>
196
197 <!-- Emulation Menu -->
198 <string name="emulation_exit">エミュレーションを終了</string>
199 <string name="emulation_done">完了</string>
200 <string name="emulation_fps_counter">FPSカウンター</string>
201 <string name="emulation_toggle_controls">コントロールを切り替え</string>
202 <string name="emulation_dpad_slide">十字キーのスライド操作</string>
203 <string name="emulation_haptics">振動</string>
204 <string name="emulation_show_overlay">オーバーレイを表示</string>
205 <string name="emulation_toggle_all">すべて選択</string>
206 <string name="emulation_control_adjust">オーバーレイを調整</string>
207 <string name="emulation_control_scale">大きさ</string>
208 <string name="emulation_control_opacity">不透明度</string>
209 <string name="emulation_touch_overlay_reset">リセット</string>
210 <string name="emulation_touch_overlay_edit">オーバーレイを編集</string>
211 <string name="emulation_pause">エミュレーションを一時停止</string>
212 <string name="emulation_unpause">エミュレーションを再開</string>
213 <string name="emulation_input_overlay">オーバーレイオプション</string>
214 <string name="emulation_game_loading">ロード中…</string>
215
216 <string name="load_settings">設定をロード中…</string>
217
218 <!-- Software keyboard -->
219 <string name="software_keyboard">ソフトウェアキーボード</string>
220
221 <!-- Errors and warnings -->
222 <string name="abort_button">中断</string>
223 <string name="continue_button">続行</string>
224 <string name="system_archive_not_found">システムアーカイブが見つかりません</string>
225 <string name="system_archive_not_found_message">%s が見つかりません。システムアーカイブをダンプしてください。\nエミュレーションを続行すると、クラッシュやバグが発生する可能性があります。</string>
226 <string name="system_archive_general">システムアーカイブ</string>
227 <string name="save_load_error">セーブ/ロード エラー</string>
228 <string name="fatal_error">致命的なエラー</string>
229 <string name="fatal_error_message">致命的なエラーが発生しました。詳細はログを確認してください。\nエミュレーションを続行するとクラッシュやバグが発生する可能性があります。</string>
230 <string name="performance_warning">この設定をオフにすると、エミュレーションのパフォーマンスが著しく低下します!最高の体験を得るためには、この設定を有効にしておくことをお勧めします。</string>
231
232 <!-- Region Names -->
233 <string name="region_japan">日本</string>
234 <string name="region_usa">アメリカ</string>
235 <string name="region_europe">ヨーロッパ</string>
236 <string name="region_australia">オーストラリア</string>
237 <string name="region_china">中国</string>
238 <string name="region_korea">韓国</string>
239 <string name="region_taiwan">台湾</string>
240
241 <!-- Language Names -->
242 <string name="language_japanese">日本語</string>
243 <string name="language_english">英語</string>
244 <string name="language_french">フランス語 (Français)</string>
245 <string name="langauge_german">ドイツ語 (Deutsch)</string>
246 <string name="language_italian">イタリア語 (Italiano)</string>
247 <string name="language_spanish">スペイン語 (Español)</string>
248 <string name="language_chinese">中国語 (简体中文)</string>
249 <string name="language_korean">韓国語 (한국어)</string>
250 <string name="language_dutch">オランダ語 (Nederlands)</string>
251 <string name="language_portuguese">ポルトガル語 (Português)</string>
252 <string name="language_russian">ロシア語 (Русский)</string>
253 <string name="language_taiwanese">台湾語 (台湾)</string>
254 <string name="language_british_english">イギリス英語</string>
255 <string name="language_canadian_french">フランス語(カナダ) (Français canadien)</string>
256 <string name="language_latin_american_spanish">スペイン語(ラテンアメリカ) (Español latinoamericano)</string>
257 <string name="language_simplified_chinese">中国語 (简体中文)</string>
258 <string name="language_traditional_chinese">繁体字中国語 (正體中文)</string>
259 <string name="language_brazilian_portuguese">ポルトガル語(ブラジル) (Português do Brasil)</string>
260
261 <!-- Renderer APIs -->
262 <string name="renderer_vulkan">Vulkan</string>
263 <string name="renderer_none">なし</string>
264
265 <!-- Renderer Accuracy -->
266 <string name="renderer_accuracy_normal">標準</string>
267 <string name="renderer_accuracy_high">高い</string>
268 <string name="renderer_accuracy_extreme">最高 (低速)</string>
269
270 <!-- Resolutions -->
271 <string name="resolution_half">0.5X (360p/540p)</string>
272 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
273 <string name="resolution_one">1X (720p/1080p)</string>
274 <string name="resolution_two">2X (1440p/2160p) (低速)</string>
275 <string name="resolution_three">3X (2160p/3240p) (低速)</string>
276 <string name="resolution_four">4X (2880p/4320p) (低速)</string>
277
278 <!-- Renderer VSync -->
279 <string name="renderer_vsync_immediate">Immediate (オフ)</string>
280 <string name="renderer_vsync_mailbox">Mailbox</string>
281 <string name="renderer_vsync_fifo">FIFO (オン)</string>
282 <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
283
284 <!-- Scaling Filters -->
285 <string name="scaling_filter_nearest_neighbor">Nearest Neighbor</string>
286 <string name="scaling_filter_bilinear">Bilinear</string>
287 <string name="scaling_filter_bicubic">Bicubic</string>
288 <string name="scaling_filter_gaussian">Gaussian</string>
289 <string name="scaling_filter_scale_force">ScaleForce</string>
290 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
291
292 <!-- Anti-Aliasing -->
293 <string name="anti_aliasing_none">なし</string>
294 <string name="anti_aliasing_fxaa">FXAA</string>
295 <string name="anti_aliasing_smaa">SMAA</string>
296
297 <!-- Aspect Ratios -->
298 <string name="ratio_default">デフォルト (16:9)</string>
299 <string name="ratio_force_four_three">強制 4:3</string>
300 <string name="ratio_force_twenty_one_nine">強制 21:9</string>
301 <string name="ratio_force_sixteen_ten">強制 16:10</string>
302 <string name="ratio_stretch">ウィンドウに合わせる</string>
303
304 <!-- CPU Accuracy -->
305 <string name="cpu_accuracy_accurate">正確</string>
306 <string name="cpu_accuracy_unsafe">不安定</string>
307 <string name="cpu_accuracy_paranoid">パラノイド (低速)</string>
308
309 <!-- Gamepad Buttons -->
310 <string name="gamepad_d_pad">方向ボタン</string>
311 <string name="gamepad_left_stick">Lスティック</string>
312 <string name="gamepad_right_stick">Rスティック</string>
313 <string name="gamepad_home">HOMEボタン</string>
314 <string name="gamepad_screenshot">スクリーンショット</string>
315
316 <!-- Disk shader cache -->
317 <string name="preparing_shaders">シェーダーを準備しています</string>
318 <string name="building_shaders">シェーダーを構築しています</string>
319
320 <!-- Theme options -->
321 <string name="change_app_theme">アプリのテーマ</string>
322 <string name="theme_default">デフォルト</string>
323 <string name="theme_material_you">Material You</string>
324
325 <!-- Theme Modes -->
326 <string name="change_theme_mode">テーマモード</string>
327 <string name="theme_mode_follow_system">システムに従う</string>
328 <string name="theme_mode_light">ライト</string>
329 <string name="theme_mode_dark">ダーク</string>
330
331 <!-- Black backgrounds theme -->
332 <string name="use_black_backgrounds">黒色の背景を使用</string>
333 <string name="use_black_backgrounds_description">ダークテーマの使用時は、黒色の背景を有効にしてください。</string>
334
335</resources>
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
new file mode 100644
index 000000000..5da80ab4b
--- /dev/null
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">이 소프트웨어는 닌텐도 스위치 게임 콘솔용 게임을 실행합니다. 게임 타이틀이나 keys는 포함되어 있지 않습니다.&lt;br /&gt;&lt;br /&gt;시작하기 전에 장치 저장소에서 <![CDATA[<b> prod.keys </b>]]> 파일을 찾아주세요.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">자세히 알아보기</a>]]></string>
5 <string name="emulation_notification_channel_name">에뮬레이션이 활성화됨</string>
6 <string name="emulation_notification_channel_description">에뮬레이션이 실행 중일 때 영구 알림을 표시합니다.</string>
7 <string name="emulation_notification_running">yuzu가 실행 중입니다.</string>
8 <string name="notice_notification_channel_name">알림 및 오류</string>
9 <string name="notice_notification_channel_description">문제가 발생하면 알림을 표시합니다.</string>
10 <string name="notification_permission_not_granted">알림 권한이 부여되지 않았습니다!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">환영합니다!</string>
14 <string name="welcome_description">&lt;b>yuzu&lt;/b> 를 설정하고 에뮬레이션으로 이동하는 방법을 알아보세요.</string>
15 <string name="get_started">시작하기</string>
16 <string name="keys">Keys</string>
17 <string name="keys_description">아래 버튼을 사용하여 &lt;b>prod.keys&lt;/b> 파일을 선택합니다.</string>
18 <string name="select_keys">keys 선택</string>
19 <string name="games">게임</string>
20 <string name="games_description">아래 버튼으로 &lt;b>게임&lt;/b> 폴더를 선택합니다.</string>
21 <string name="done">완료</string>
22 <string name="done_description">모든 준비가 완료되었습니다.\n게임을 즐기세요!</string>
23 <string name="text_continue">계속</string>
24 <string name="next">다음</string>
25 <string name="back">뒤로</string>
26 <string name="add_games">게임 추가</string>
27 <string name="add_games_description">게임 폴더 선택</string>
28
29 <!-- Home strings -->
30 <string name="home_games">게임</string>
31 <string name="home_search">검색</string>
32 <string name="home_settings">설정</string>
33 <string name="empty_gamelist">파일을 찾을 수 없거나 아직 게임 디렉토리를 선택하지 않았습니다.</string>
34 <string name="search_and_filter_games">게임 검색 및 필터링</string>
35 <string name="select_games_folder">게임 폴더 선택</string>
36 <string name="select_games_folder_description">yuzu가 게임 목록을 채울 수 있도록 허용</string>
37 <string name="add_games_warning">게임 폴더 선택을 건너뛰겠습니까?</string>
38 <string name="add_games_warning_description">폴더를 선택하지 않으면 게임 목록에 게임이 표시되지 않습니다.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">게임 검색</string>
41 <string name="games_dir_selected">게임 디렉터리 선택</string>
42 <string name="install_prod_keys">prod.keys 설치</string>
43 <string name="install_prod_keys_description">판매용 게임 암호 해독에 요구</string>
44 <string name="install_prod_keys_warning">keys 추가를 건너뛰겠습니까?</string>
45 <string name="install_prod_keys_warning_description">정품 게임을 에뮬레이트하려면 유효한 keys가 필요합니다. 계속하면 자체 제작 앱만 작동합니다.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">알림</string>
48 <string name="notifications_description">아래 버튼으로 알림 권한을 부여합니다.</string>
49 <string name="give_permission">권한 부여</string>
50 <string name="notification_warning">알림 권한 부여를 건너뛰겠습니까?</string>
51 <string name="notification_warning_description">yuzu는 중요한 정보를 알려드리지 않습니다.</string>
52 <string name="permission_denied">권한 거부됨</string>
53 <string name="permission_denied_description">이 권한을 너무 많이 거부했으므로 이제 시스템 설정에서 수동으로 권한을 부여해야 합니다.</string>
54 <string name="about">정보</string>
55 <string name="about_description">빌드 버전, 크레딧 등</string>
56 <string name="warning_help">도움말</string>
57 <string name="warning_skip">건너뛰기</string>
58 <string name="warning_cancel">취소</string>
59 <string name="install_amiibo_keys">Amiibo keys 설치</string>
60 <string name="install_amiibo_keys_description">게임에서 아미보 사용 시 필요</string>
61 <string name="invalid_keys_file">잘못된 keys 파일 선택</string>
62 <string name="install_keys_success">keys가 성공적으로 설치됨</string>
63 <string name="reading_keys_failure">암호화 keys 읽기 오류</string>
64 <string name="invalid_keys_error">잘못된 암호화 keys</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">선택한 파일이 잘못되었거나 손상되었습니다. keys를 다시 덤프하세요.</string>
67 <string name="install_gpu_driver">GPU 드라이버 설치</string>
68 <string name="install_gpu_driver_description">잠재적으로 더 나은 성능 또는 정확성을 위해 대체 드라이버를 설치하세요.</string>
69 <string name="advanced_settings">고급 설정</string>
70 <string name="settings_description">에뮬레이터 설정 구성</string>
71 <string name="search_recently_played">최근 플레이한 게임</string>
72 <string name="search_recently_added">최근 추가한 게임</string>
73 <string name="search_retail">판매용</string>
74 <string name="search_homebrew">홈브류</string>
75 <string name="open_user_folder">yuzu 폴더 열기</string>
76 <string name="open_user_folder_description">yuzu의 내부 파일 관리</string>
77 <string name="theme_and_color_description">앱 모양 수정</string>
78 <string name="no_file_manager">파일 관리자를 찾을 수 없음</string>
79 <string name="notification_no_directory_link">yuzu 디렉토리를 열 수 없음</string>
80 <string name="notification_no_directory_link_description">파일 관리자의 사이드 패널에서 사용자 폴더를 수동으로 찾아주세요.</string>
81 <string name="manage_save_data">저장 데이터 관리</string>
82 <string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string>
83 <string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string>
84 <string name="import_export_saves_no_profile">저장 데이터를 찾을 수 없습니다. 게임을 실행한 후 다시 시도하세요.</string>
85 <string name="save_file_imported_success">가져오기 성공</string>
86 <string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</string>
87 <string name="save_file_invalid_zip_structure_description">첫 번째 하위 폴더 이름은 게임의 타이틀 ID여야 합니다.</string>
88 <string name="import_saves">가져오기</string>
89 <string name="export_saves">내보내기</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">가이아는 진짜가 아님</string>
93 <string name="copied_to_clipboard">클립보드에 복사</string>
94 <string name="about_app_description">오픈 소스 스위치 에뮬레이터</string>
95 <string name="contributors">기여자</string>
96 <string name="contributors_description">yuzu 팀의 \u2764로 제작</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">빌드</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">미리 체험하기</string>
105 <string name="get_early_access">미리 체험하기 신청</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">최첨단 기능, 미리 체험하기 업데이트 등</string>
108 <string name="early_access_benefits">미리 체험하기 혜택</string>
109 <string name="cutting_edge_features">최첨단 기능</string>
110 <string name="early_access_updates">미리 체험하기 업데이트</string>
111 <string name="no_manual_installation">수동 설치 불필요</string>
112 <string name="prioritized_support">우선 지원</string>
113 <string name="helping_game_preservation">게임 보존 도움주기</string>
114 <string name="our_eternal_gratitude">영원한 감사의 마음을 전합니다</string>
115 <string name="are_you_interested">관심 있으세요?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">제한 속도 활성화</string>
119 <string name="frame_limit_enable_description">활성화하면 에뮬레이션 속도가 정상 속도의 지정된 비율로 제한됩니다.</string>
120 <string name="frame_limit_slider">속도 제한 비율</string>
121 <string name="frame_limit_slider_description">에뮬레이션 속도를 제한할 비율을 지정합니다. 기본값인 100%로 설정하면 에뮬레이션이 정상 속도로 제한됩니다. 값이 높거나 낮으면 속도 제한이 증가하거나 감소합니다.</string>
122 <string name="cpu_accuracy">CPU 정확도</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">도킹 모드</string>
126 <string name="use_docked_mode_description">도킹 모드에서 에뮬레이션하면 성능이 저하되는 대신 해상도가 향상됩니다.</string>
127 <string name="emulated_region">에뮬레이트된 지역</string>
128 <string name="emulated_language">에뮬레이트된 언어</string>
129 <string name="select_rtc_date">RTC 날짜 선택</string>
130 <string name="select_rtc_time">RTC 시간 선택</string>
131 <string name="use_custom_rtc">커스텀 RTC 활성화</string>
132 <string name="use_custom_rtc_description">이 설정을 사용하면 현재 시스템 시간과 별도로 사용자 지정 실시간 시계를 설정할 수 있음</string>
133 <string name="set_custom_rtc">커스텀 RTC 설정</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">정확도 수준</string>
138 <string name="renderer_resolution">해상도</string>
139 <string name="renderer_vsync">수직동기화 모드</string>
140 <string name="renderer_aspect_ratio">화면비</string>
141 <string name="renderer_scaling_filter">창 적응 필터</string>
142 <string name="renderer_anti_aliasing">안티-에일리어싱 방법</string>
143 <string name="renderer_force_max_clock">최대 클럭 강제 설정 (아드레노만 해당)</string>
144 <string name="renderer_force_max_clock_description">GPU가 가능한 최대 클럭으로 실행되도록 강제합니다 (열 제약 조건은 여전히 적용됩니다).</string>
145 <string name="renderer_asynchronous_shaders">비동기 셰이더 사용</string>
146 <string name="renderer_asynchronous_shaders_description">셰이더를 비동기식으로 컴파일하므로 끊김 현상이 줄어들지만 글리치가 발생할 수 있습니다.</string>
147 <string name="renderer_debug">그래픽 디버깅 활성화</string>
148 <string name="renderer_debug_description">이 옵션을 선택하면 그래픽 API가 느린 디버깅 모드로 전환됩니다.</string>
149 <string name="use_disk_shader_cache">디스크 셰이더 캐시 사용</string>
150 <string name="use_disk_shader_cache_description">생성된 셰이더를 디스크에 저장하고 불러오기하여 끊김 현상을 줄입니다.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">볼륨</string>
154 <string name="audio_volume_description">오디오 출력의 볼륨을 지정합니다.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">기본값</string>
158 <string name="ini_saved">저장된 설정</string>
159 <string name="gameid_saved">%1$s를 위해 저장된 설정</string>
160 <string name="error_saving">%1$s.ini 저장 중 오류: %2$s</string>
161 <string name="loading">불러오기 중...</string>
162 <string name="reset_setting_confirmation">이 설정을 기본값으로 되돌리겠습니까?</string>
163 <string name="reset_to_default">기본값으로 재설정</string>
164 <string name="reset_all_settings">모든 설정을 초기화하겠습니까?</string>
165 <string name="reset_all_settings_description">모든 고급 설정이 기본 구성으로 재설정됩니다. 이 설정은 되돌릴 수 없습니다.</string>
166 <string name="settings_reset">설정 초기화</string>
167 <string name="close">닫기</string>
168 <string name="learn_more">자세히 알아보기</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">GPU 드라이버 선택</string>
172 <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
173 <string name="select_gpu_driver_install">설치</string>
174 <string name="select_gpu_driver_default">기본값</string>
175 <string name="select_gpu_driver_install_success">설치된 %s</string>
176 <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
177 <string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
178 <string name="system_gpu_driver">시스템 GPU 드라이버</string>
179 <string name="installing_driver">드라이버 설치 중...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">설정</string>
183 <string name="preferences_general">일반</string>
184 <string name="preferences_system">시스템</string>
185 <string name="preferences_graphics">그래픽</string>
186 <string name="preferences_audio">오디오</string>
187 <string name="preferences_theme">테마 및 색상</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">롬이 암호화되었음</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[가이드에 따라 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">게임 카트리지</a> 또는 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">설치된 타이틀</a>를 다시 덤프하세요.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[P게임을 해독할 수 있도록 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> 파일이 설치되어 있는지 확인하세요.]]></string>
193 <string name="loader_error_video_core">비디오 코어를 초기화하는 동안 오류 발생</string>
194 <string name="loader_error_video_core_description">이 문제는 일반적으로 호환되지 않는 GPU 드라이버로 인해 발생합니다. 사용자 지정 GPU 드라이버를 설치하면 이 문제가 해결될 수 있습니다.</string>
195 <string name="loader_error_invalid_format">롬을 불러올 수 없음</string>
196 <string name="loader_error_file_not_found">롬 파일이 존재하지 않음</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">에뮬레이션 종료</string>
200 <string name="emulation_done">완료</string>
201 <string name="emulation_fps_counter">FPS 카운터</string>
202 <string name="emulation_toggle_controls">토글 제어</string>
203 <string name="emulation_rel_stick_center">상대 스틱 센터</string>
204 <string name="emulation_dpad_slide">십자패드 슬라이드</string>
205 <string name="emulation_haptics">햅틱</string>
206 <string name="emulation_show_overlay">오버레이 표시</string>
207 <string name="emulation_toggle_all">모두 토글</string>
208 <string name="emulation_control_adjust">오버레이 조정</string>
209 <string name="emulation_control_scale">스케일</string>
210 <string name="emulation_control_opacity">불투명도</string>
211 <string name="emulation_touch_overlay_reset">오버레이 재설정</string>
212 <string name="emulation_touch_overlay_edit">오버레이 편집</string>
213 <string name="emulation_pause">에뮬레이션 일시 중지</string>
214 <string name="emulation_unpause">에뮬레이션 일시 중지 해제</string>
215 <string name="emulation_input_overlay">오버레이 옵션</string>
216 <string name="emulation_game_loading">게임 불러오기 중...</string>
217
218 <string name="load_settings">설정 불러오기 중...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">가상 키보드</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">정보</string>
225 <string name="continue_button">계속</string>
226 <string name="system_archive_not_found">시스템 아카이브를 찾을 수 없음</string>
227 <string name="system_archive_not_found_message">%s가 누락되었습니다. 시스템 아카이브를 덤프하세요.\n에뮬레이션을 계속하면 충돌 및 버그가 발생할 수 있습니다.</string>
228 <string name="system_archive_general">시스템 아카이브</string>
229 <string name="save_load_error">저장하기/불러오기 오류</string>
230 <string name="fatal_error">치명적인 오류</string>
231 <string name="fatal_error_message">치명적인 오류가 발생했습니다. 자세한 내용은 로그를 확인하십시오.\n에뮬레이션을 계속하면 충돌 및 버그가 발생할 수 있습니다.</string>
232 <string name="performance_warning">이 설정을 끄면 에뮬레이션 성능이 크게 저하됩니다! 최상의 환경을 위해 이 설정을 활성화된 상태로 두는 것이 좋습니다.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">일본</string>
236 <string name="region_usa">미국</string>
237 <string name="region_europe">유럽</string>
238 <string name="region_australia">호주</string>
239 <string name="region_china">중국</string>
240 <string name="region_korea">대한민국</string>
241 <string name="region_taiwan">타이완</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">일본어 (日本語)</string>
245 <string name="language_english">영어 (English)</string>
246 <string name="language_french">프랑스어 (Français)</string>
247 <string name="langauge_german">독일어(Deutsch)</string>
248 <string name="language_italian">이탈리아어 (Italiano)</string>
249 <string name="language_spanish">스페인어 (Español)</string>
250 <string name="language_chinese">중국어 (简体中文)</string>
251 <string name="language_korean">한국어 (Korean)</string>
252 <string name="language_dutch">네덜란드어 (Nederlands)</string>
253 <string name="language_portuguese">포르투갈어 (Português)</string>
254 <string name="language_russian">러시아어 (Русский)</string>
255 <string name="language_taiwanese">대만어 (台湾)</string>
256 <string name="language_british_english">영어 (British English)</string>
257 <string name="language_canadian_french">캐나다 프랑스어 (Français canadien)</string>
258 <string name="language_latin_american_spanish">라틴 아메리카 스페인어 (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">중국어 간체 (简体中文)</string>
260 <string name="language_traditional_chinese">중국어 번체 (正體中文)</string>
261 <string name="language_brazilian_portuguese">브라질 포르투갈어 (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">불칸</string>
265 <string name="renderer_none">없음</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">보통</string>
269 <string name="renderer_accuracy_high">높음</string>
270 <string name="renderer_accuracy_extreme">극한 (느림)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (느림)</string>
277 <string name="resolution_three">3X (2160p/3240p) (느림)</string>
278 <string name="resolution_four">4X (2880p/4320p) (느림)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">즉시 (끔)</string>
282 <string name="renderer_vsync_mailbox">메일박스</string>
283 <string name="renderer_vsync_fifo">FIFO (켬)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO 릴랙스</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">가장 가까운 이웃</string>
288 <string name="scaling_filter_bilinear">이중선형</string>
289 <string name="scaling_filter_bicubic">고등차수보간</string>
290 <string name="scaling_filter_gaussian">가우시안</string>
291 <string name="scaling_filter_scale_force">스케일포스</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™ 초고해상도</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">없음</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">기본 (16:9)</string>
301 <string name="ratio_force_four_three">강제 4:3</string>
302 <string name="ratio_force_twenty_one_nine">강제 21:9</string>
303 <string name="ratio_force_sixteen_ten">강제 16:10</string>
304 <string name="ratio_stretch">창에 맞게 늘림</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">정확함</string>
308 <string name="cpu_accuracy_unsafe">안전하지 않음</string>
309 <string name="cpu_accuracy_paranoid">편집증 (느림)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">십자패드</string>
313 <string name="gamepad_left_stick">L 스틱</string>
314 <string name="gamepad_right_stick">R 스틱</string>
315 <string name="gamepad_home">홈</string>
316 <string name="gamepad_screenshot">스크린샷</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">셰이더 준비하기</string>
320 <string name="building_shaders">셰이더 빌드 중</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">앱 테마 변경</string>
324 <string name="theme_default">기본값</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">테마 모드 변경</string>
329 <string name="theme_mode_follow_system">팔로우 시스템</string>
330 <string name="theme_mode_light">밝음</string>
331 <string name="theme_mode_dark">어두움</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">검은색 배경 사용</string>
335 <string name="use_black_backgrounds_description">어두운 테마를 사용할 때는 검은색 배경을 적용합니다.</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
new file mode 100644
index 000000000..3e1f9bce5
--- /dev/null
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Denne programvaren vil kjøre spill for Nintendo Switch-spillkonsollen. Ingen spilltitler eller nøkler er inkludert.&lt;br /&gt;&lt;br /&gt;Før du begynner, må du finne <![CDATA[<b> prod.keys </b>]]> filen din på enhetslagringen.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Lær mer</a>]]></string>
5 <string name="emulation_notification_channel_name">Emulering er aktiv</string>
6 <string name="emulation_notification_channel_description">Viser et vedvarende varsel når emuleringen kjører.</string>
7 <string name="emulation_notification_running">Yuzu kjører</string>
8 <string name="notice_notification_channel_name">Merknader og feil</string>
9 <string name="notice_notification_channel_description">Viser varsler når noe går galt.</string>
10 <string name="notification_permission_not_granted">Varslingstillatelse ikke gitt!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Velkommen!</string>
14 <string name="welcome_description">Lær å sette opp &lt;b>yuzu&lt;/b> og hopp inn i emulering.</string>
15 <string name="get_started">Kom i gang</string>
16 <string name="keys">Nøkler</string>
17 <string name="keys_description">Velg din &lt;b>prod.keys&lt;/b> fil ved å bruke knappen under.</string>
18 <string name="select_keys">Velg nøkler</string>
19 <string name="games">Spill</string>
20 <string name="games_description">Velg din &lt;b>Spill&lt;/b> mappe ved å bruke knappen under.</string>
21 <string name="done">Ferdig</string>
22 <string name="done_description">Nå er du klar.\nGled deg til å spille!</string>
23 <string name="text_continue">Fortsett</string>
24 <string name="next">Neste</string>
25 <string name="back">Tilbake</string>
26 <string name="add_games">Legg til spill</string>
27 <string name="add_games_description">Velg din spillmappe</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Spill</string>
31 <string name="home_search">Søk</string>
32 <string name="home_settings">Innstillinger</string>
33 <string name="empty_gamelist">Ingen filer ble funnet eller ingen spillkatalog er valgt ennå.</string>
34 <string name="search_and_filter_games">Søk og filtrer spill</string>
35 <string name="select_games_folder">Velg spillmappe</string>
36 <string name="select_games_folder_description">Gjør det mulig for yuzu å fylle ut spillelisten.</string>
37 <string name="add_games_warning">Hoppe over valg av spillmappe?</string>
38 <string name="add_games_warning_description">Spill vises ikke i Spill-listen hvis en mappe ikke er valgt.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Søk i spill</string>
41 <string name="games_dir_selected">Spillkatalogen er valgt</string>
42 <string name="install_prod_keys">Installer prod.keys</string>
43 <string name="install_prod_keys_description">Nødvendig for å dekryptere spill</string>
44 <string name="install_prod_keys_warning">Hoppe over å legge til nøkler?</string>
45 <string name="install_prod_keys_warning_description">Gyldige nøkler er påkrevd for å emulere spill. Bare hjemmebryggede apper vil fungere hvis du fortsetter.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Varsler</string>
48 <string name="notifications_description">Gi varslingstillatelse med knappen nedenfor.</string>
49 <string name="give_permission">Gi tillatelse</string>
50 <string name="notification_warning">Hoppe over å gi tillatelse til varsling?</string>
51 <string name="notification_warning_description">yuzu vil ikke kunne varsle deg om viktig informasjon.</string>
52 <string name="permission_denied">Tillatelse avslått</string>
53 <string name="permission_denied_description">Du har nektet denne tillatelsen for mange ganger, og nå må du gi den manuelt i systeminnstillingene.</string>
54 <string name="about">Om</string>
55 <string name="about_description">Byggeversjon, kildehenvisninger og mer</string>
56 <string name="warning_help">Hjelp</string>
57 <string name="warning_skip">Hopp over</string>
58 <string name="warning_cancel">Avbryt</string>
59 <string name="install_amiibo_keys">Installer Amiibo-nøkler</string>
60 <string name="install_amiibo_keys_description">Kreves for å bruke Amiibo i spillet</string>
61 <string name="invalid_keys_file">Ugyldig nøkkelfil valgt</string>
62 <string name="install_keys_success">Nøkler vellykket installert</string>
63 <string name="reading_keys_failure">Feil ved lesing av krypteringsnøkler</string>
64 <string name="invalid_keys_error">Ugyldige krypteringsnøkler</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">Den valgte filen er feil eller ødelagt. Vennligst dump nøklene på nytt.</string>
67 <string name="install_gpu_driver">Installer GPU-driver</string>
68 <string name="install_gpu_driver_description">Installer alternative drivere for potensielt bedre ytelse eller nøyaktighet.</string>
69 <string name="advanced_settings">Avanserte innstillinger</string>
70 <string name="settings_description">Konfigurere emulatorinnstillinger</string>
71 <string name="search_recently_played">Nylig spilt</string>
72 <string name="search_recently_added">Nylig lagt til</string>
73 <string name="search_retail">Butikkhandel</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Åpne yuzu-mappen</string>
76 <string name="open_user_folder_description">Administrere yuzus interne filer</string>
77 <string name="theme_and_color_description">Endre appens utseende</string>
78 <string name="no_file_manager">Ingen filbehandler funnet</string>
79 <string name="notification_no_directory_link">Kunne ikke åpne yuzu-katalogen</string>
80 <string name="notification_no_directory_link_description">Finn brukermappen manuelt med filbehandlingens sidepanel.</string>
81 <string name="manage_save_data">Administrere lagringsdata</string>
82 <string name="manage_save_data_description">Lagringsdata funnet. Velg et alternativ nedenfor.</string>
83 <string name="import_export_saves_description">Importer eller eksporter lagringsfiler</string>
84 <string name="import_export_saves_no_profile">Ingen lagringsdata funnet. Start et nytt spill og prøv på nytt.</string>
85 <string name="save_file_imported_success">Vellykket import</string>
86 <string name="save_file_invalid_zip_structure">Ugyldig struktur for lagringskatalog</string>
87 <string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string>
88 <string name="import_saves">Importer</string>
89 <string name="export_saves">Eksporter</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia er ikke ekte</string>
93 <string name="copied_to_clipboard">Kopiert til utklippstavlen</string>
94 <string name="about_app_description">En Switch-emulator med åpen kildekode</string>
95 <string name="contributors">Bidragsytere</string>
96 <string name="contributors_description">Laget med \u2764 fra yuzu-teamet</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Bygg</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Tidlig tilgang</string>
105 <string name="get_early_access">Få tidlig tilgang</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Banebrytende funksjoner, tidlig tilgang til oppdateringer og mye mer.</string>
108 <string name="early_access_benefits">Fordeler ved tidlig tilgang</string>
109 <string name="cutting_edge_features">Avanserte funksjoner</string>
110 <string name="early_access_updates">Tidlig tilgang til oppdateringer</string>
111 <string name="no_manual_installation">Ingen manuell installasjon</string>
112 <string name="prioritized_support">Prioritert støtte</string>
113 <string name="helping_game_preservation">Bidra til bevaring av spill</string>
114 <string name="our_eternal_gratitude">Vår evige takknemlighet</string>
115 <string name="are_you_interested">Er du interessert?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Aktiver hastighetsbegrensning</string>
119 <string name="frame_limit_enable_description">Når aktivert, begrenses emuleringshastigheten til en angitt prosentandel av normal hastighet.</string>
120 <string name="frame_limit_slider">Hastighetsbegrensning i prosent</string>
121 <string name="frame_limit_slider_description">Angir prosentandelen som skal begrense emuleringshastigheten. Med standardverdien 100 % vil emuleringen være begrenset til normal hastighet. Høyere eller lavere verdier vil øke eller redusere hastighetsbegrensningen.</string>
122 <string name="cpu_accuracy">CPU-nøyaktighet</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Dokket modus</string>
126 <string name="use_docked_mode_description">Emulerer i dokket modus, noe som øker oppløsningen på bekostning av ytelsen.</string>
127 <string name="emulated_region">Emulert region</string>
128 <string name="emulated_language">Emulert språk</string>
129 <string name="select_rtc_date">Velg RTC-dato</string>
130 <string name="select_rtc_time">Velg RTC-tid</string>
131 <string name="use_custom_rtc">Aktiver egendefinert RTC</string>
132 <string name="use_custom_rtc_description">Med denne innstillingen kan du stille inn en egendefinert sanntidsklokke som er atskilt fra gjeldende systemtid.</string>
133 <string name="set_custom_rtc">Angi egendefinert RTC</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">Nøyaktighetsnivå</string>
138 <string name="renderer_resolution">Oppløsning</string>
139 <string name="renderer_vsync">VSync-modus</string>
140 <string name="renderer_aspect_ratio">Størrelsesforhold</string>
141 <string name="renderer_scaling_filter">Filter for vindustilpasning</string>
142 <string name="renderer_anti_aliasing">Anti-Aliasing-metode</string>
143 <string name="renderer_force_max_clock">Tving fram maksimal klokkefrekvens (kun Adreno)</string>
144 <string name="renderer_force_max_clock_description">Tvinger GPU-en til å kjøre med maksimal klokkefrekvens (termiske begrensninger vil fortsatt gjelde).</string>
145 <string name="renderer_asynchronous_shaders">Bruk asynkrone shaders</string>
146 <string name="renderer_asynchronous_shaders_description">Kompilerer shaders asynkront, noe som reduserer hakkingen, men kan føre til feil.</string>
147 <string name="renderer_debug">Aktiver feilsøking av grafikk</string>
148 <string name="renderer_debug_description">Når dette er merket av, går grafikk-API-et inn i en langsommere feilsøkingsmodus.</string>
149 <string name="use_disk_shader_cache">Bruk disk shader-cache</string>
150 <string name="use_disk_shader_cache_description">Reduser hakking ved å lagre og laste inn genererte shaders på disken.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Volum</string>
154 <string name="audio_volume_description">Angir volumet på lydutgangen.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">Standard</string>
158 <string name="ini_saved">Lagrede innstillinger</string>
159 <string name="gameid_saved">Lagrede innstillinger for %1$s</string>
160 <string name="error_saving">Feil ved lagring av %1$s.ini: %2$s</string>
161 <string name="loading">Lastes inn...</string>
162 <string name="reset_setting_confirmation">Vil du tilbakestille denne innstillingen til standardverdien?</string>
163 <string name="reset_to_default">Tilbakestill til standardinnstillingene</string>
164 <string name="reset_all_settings">Tilbakestille alle innstillinger?</string>
165 <string name="reset_all_settings_description">Alle avanserte innstillinger tilbakestilles til standardkonfigurasjonen. Dette kan ikke angres.</string>
166 <string name="settings_reset">Tilbakestilling av innstillinger</string>
167 <string name="close">Lukk</string>
168 <string name="learn_more">Lær Mer</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Velg GPU-driver</string>
172 <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string>
173 <string name="select_gpu_driver_install">Installer</string>
174 <string name="select_gpu_driver_default">Standard</string>
175 <string name="select_gpu_driver_install_success">Installert %s</string>
176 <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
177 <string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string>
178 <string name="system_gpu_driver">Systemets GPU-driver</string>
179 <string name="installing_driver">Installerer driver...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Innstillinger</string>
183 <string name="preferences_general">Generelt</string>
184 <string name="preferences_system">System</string>
185 <string name="preferences_graphics">Grafikk</string>
186 <string name="preferences_audio">Lyd</string>
187 <string name="preferences_theme">Tema og farge</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">ROM-en din er kryptert</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Følg veiledningene for å redumpe dine <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">spillkassetter</a> eller <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">installerte titler</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Vennligst sørg for at <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> filen er installert slik at spillene kan dekrypteres.]]></string>
193 <string name="loader_error_video_core">Det oppstod en feil ved initialisering av videokjernen</string>
194 <string name="loader_error_video_core_description">Dette skyldes vanligvis en inkompatibel GPU-driver. Installering av en tilpasset GPU-driver kan løse problemet.</string>
195 <string name="loader_error_invalid_format">Kunne ikke laste inn ROM</string>
196 <string name="loader_error_file_not_found">ROM-filen finnes ikke</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Avslutt emulering</string>
200 <string name="emulation_done">Ferdig</string>
201 <string name="emulation_fps_counter">FPS-teller</string>
202 <string name="emulation_toggle_controls">Veksle kontroller</string>
203 <string name="emulation_rel_stick_center">Relativt senter for stikken</string>
204 <string name="emulation_dpad_slide">DPad-skyveplate</string>
205 <string name="emulation_haptics">Haptikk</string>
206 <string name="emulation_show_overlay">Vis overlegg</string>
207 <string name="emulation_toggle_all">Slå av alt</string>
208 <string name="emulation_control_adjust">Juster overlegg</string>
209 <string name="emulation_control_scale">Skaler</string>
210 <string name="emulation_control_opacity">Gjennomsiktighet</string>
211 <string name="emulation_touch_overlay_reset">Tilbakestill overlegg</string>
212 <string name="emulation_touch_overlay_edit">Rediger overlegg</string>
213 <string name="emulation_pause">Pause Emulering</string>
214 <string name="emulation_unpause">Opphev pausing av emulering</string>
215 <string name="emulation_input_overlay">Alternativer for overlegg</string>
216 <string name="emulation_game_loading">Spillet lastes inn...</string>
217
218 <string name="load_settings">Laster inn innstillinger...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Programvare Tastatur</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Avbryt</string>
225 <string name="continue_button">Fortsett</string>
226 <string name="system_archive_not_found">System Arkiv Ikke Funnet</string>
227 <string name="system_archive_not_found_message">%s mangler. Dump systemarkivene dine.\nFortsatt emulering kan føre til krasj og feil.</string>
228 <string name="system_archive_general">Et systemarkiv</string>
229 <string name="save_load_error">Feil ved lagring/innlasting</string>
230 <string name="fatal_error">Fatal Feil</string>
231 <string name="fatal_error_message">Det oppstod en fatal feil. Sjekk loggen for mer informasjon.\nFortsatt emulering kan føre til krasj og feil.</string>
232 <string name="performance_warning">Hvis du slår av denne innstillingen, reduseres emuleringsytelsen betydelig! Vi anbefaler at du lar denne innstillingen være aktivert for å få den beste opplevelsen.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Japan</string>
236 <string name="region_usa">USA</string>
237 <string name="region_europe">Europa</string>
238 <string name="region_australia">Australia</string>
239 <string name="region_china">Kina</string>
240 <string name="region_korea">Korea</string>
241 <string name="region_taiwan">Taiwan</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Japansk (日本語)</string>
245 <string name="language_english">Engelsk</string>
246 <string name="language_french">Fransk (Français)</string>
247 <string name="langauge_german">Tysk (Deutsch)</string>
248 <string name="language_italian">Italiensk (Italiano)</string>
249 <string name="language_spanish">Spansk (Español)</string>
250 <string name="language_chinese">Kinesisk (简体中文)</string>
251 <string name="language_korean">Koreansk (한국어)</string>
252 <string name="language_dutch">Nederlandsk (Nederlands)</string>
253 <string name="language_portuguese">Portugisisk (Português)</string>
254 <string name="language_russian">Russisk (Русский)</string>
255 <string name="language_taiwanese">Taiwansk (台湾)</string>
256 <string name="language_british_english">Britisk Engelsk</string>
257 <string name="language_canadian_french">Kanadisk fransk (Français canadien)</string>
258 <string name="language_latin_american_spanish">Latinamerikansk spansk (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">Forenklet kinesisk (简体中文)</string>
260 <string name="language_traditional_chinese">Tradisjonell Kinesisk (正體中文)</string>
261 <string name="language_brazilian_portuguese">Brasiliansk portugisisk (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">Ingen</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Normal</string>
269 <string name="renderer_accuracy_high">Høy</string>
270 <string name="renderer_accuracy_extreme">Ekstrem (Treg)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Slow)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Slow)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Slow)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Umiddelbar (av)</string>
282 <string name="renderer_vsync_mailbox">Postkasse</string>
283 <string name="renderer_vsync_fifo">FIFO (På)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO avslappet</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Nærmeste nabo</string>
288 <string name="scaling_filter_bilinear">Bilineær</string>
289 <string name="scaling_filter_bicubic">Bikubisk</string>
290 <string name="scaling_filter_gaussian">Gaussisk</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Ingen</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">Standard (16:9)</string>
301 <string name="ratio_force_four_three">Tving 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Tving 21:9</string>
303 <string name="ratio_force_sixteen_ten">Tving 16:10</string>
304 <string name="ratio_stretch">Strekk til Vindu</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Nøyaktig</string>
308 <string name="cpu_accuracy_unsafe">Utrygt</string>
309 <string name="cpu_accuracy_paranoid">Paranoid (Langsom)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">D-Pad</string>
313 <string name="gamepad_left_stick">Venstre Pinne</string>
314 <string name="gamepad_right_stick">Høyre Pinne</string>
315 <string name="gamepad_home">Hjem</string>
316 <string name="gamepad_screenshot">Skjermbilde</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">Forberedelse av shaders</string>
320 <string name="building_shaders">Bygging av shaders</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Endre appens tema</string>
324 <string name="theme_default">Standard</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Endre temamodus</string>
329 <string name="theme_mode_follow_system">Følg systemet</string>
330 <string name="theme_mode_light">Lys</string>
331 <string name="theme_mode_dark">Mørk</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Bruk svart bakgrunn</string>
335 <string name="use_black_backgrounds_description">Bruk svart bakgrunn når du bruker det mørke temaet.</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-night-v31/themes.xml b/src/android/app/src/main/res/values-night-v31/themes.xml
new file mode 100644
index 000000000..631d7fd1b
--- /dev/null
+++ b/src/android/app/src/main/res/values-night-v31/themes.xml
@@ -0,0 +1,31 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <style name="Theme.Yuzu.Main.MaterialYou" parent="Theme.Yuzu.Main">
5 <item name="colorPrimary">@color/m3_sys_color_dynamic_dark_primary</item>
6 <item name="colorOnPrimary">@color/m3_sys_color_dynamic_dark_on_primary</item>
7 <item name="colorPrimaryContainer">@color/m3_sys_color_dynamic_dark_primary_container</item>
8 <item name="colorOnPrimaryContainer">@color/m3_sys_color_dynamic_dark_on_primary_container</item>
9 <item name="colorSecondary">@color/m3_sys_color_dynamic_dark_secondary</item>
10 <item name="colorOnSecondary">@color/m3_sys_color_dynamic_dark_on_secondary</item>
11 <item name="colorSecondaryContainer">@color/m3_sys_color_dynamic_dark_secondary_container</item>
12 <item name="colorOnSecondaryContainer">@color/m3_sys_color_dynamic_dark_on_secondary_container</item>
13 <item name="colorTertiary">@color/m3_sys_color_dynamic_dark_tertiary</item>
14 <item name="colorOnTertiary">@color/m3_sys_color_dynamic_dark_on_tertiary</item>
15 <item name="colorTertiaryContainer">@color/m3_sys_color_dynamic_dark_tertiary_container</item>
16 <item name="colorOnTertiaryContainer">@color/m3_sys_color_dynamic_dark_on_tertiary_container</item>
17 <item name="android:colorBackground">@color/m3_sys_color_dynamic_dark_background</item>
18 <item name="colorOnBackground">@color/m3_sys_color_dynamic_dark_on_background</item>
19 <item name="colorSurface">@color/m3_sys_color_dynamic_dark_surface</item>
20 <item name="colorOnSurface">@color/m3_sys_color_dynamic_dark_on_surface</item>
21 <item name="colorSurfaceVariant">@color/m3_sys_color_dynamic_dark_surface_variant</item>
22 <item name="colorOnSurfaceVariant">@color/m3_sys_color_dynamic_dark_on_surface_variant</item>
23 <item name="colorOutline">@color/m3_sys_color_dynamic_dark_outline</item>
24 <item name="colorOnSurfaceInverse">@color/m3_sys_color_dynamic_dark_on_surface_variant</item>
25 <item name="colorSurfaceInverse">@color/m3_sys_color_dynamic_dark_surface_variant</item>
26 <item name="colorPrimaryInverse">@color/m3_sys_color_dynamic_dark_inverse_primary</item>
27
28 <item name="materialAlertDialogTheme">@style/ThemeOverlay.Material3.MaterialAlertDialog</item>
29 </style>
30
31</resources>
diff --git a/src/android/app/src/main/res/values-night/themes.xml b/src/android/app/src/main/res/values-night/themes.xml
new file mode 100644
index 000000000..d7d24c24d
--- /dev/null
+++ b/src/android/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <style name="ThemeOverlay.Yuzu.Dark" parent="">
5 <item name="colorSurface">@android:color/black</item>
6 <item name="android:colorBackground">@android:color/black</item>
7 </style>
8
9</resources>
diff --git a/src/android/app/src/main/res/values-night/yuzu_colors.xml b/src/android/app/src/main/res/values-night/yuzu_colors.xml
new file mode 100644
index 000000000..49d823324
--- /dev/null
+++ b/src/android/app/src/main/res/values-night/yuzu_colors.xml
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <color name="yuzu_primary">#A7DDEC</color>
5 <color name="yuzu_onPrimary">#003399</color>
6 <color name="yuzu_primaryContainer">#31323F</color>
7 <color name="yuzu_onPrimaryContainer">#D1E4FF</color>
8 <color name="yuzu_secondary">#BAC8DB</color>
9 <color name="yuzu_onSecondary">#253140</color>
10 <color name="yuzu_secondaryContainer">#3B4858</color>
11 <color name="yuzu_onSecondaryContainer">#D6E4F7</color>
12 <color name="yuzu_tertiary">#D6BEE5</color>
13 <color name="yuzu_onTertiary">#3A2948</color>
14 <color name="yuzu_tertiaryContainer">#524060</color>
15 <color name="yuzu_onTertiaryContainer">#F2DAFF</color>
16 <color name="yuzu_error">#FFB4AB</color>
17 <color name="yuzu_errorContainer">#93000A</color>
18 <color name="yuzu_onError">#690005</color>
19 <color name="yuzu_onErrorContainer">#FFDAD6</color>
20 <color name="yuzu_background">#1A1C1E</color>
21 <color name="yuzu_onBackground">#E2E2E6</color>
22 <color name="yuzu_surface">#1B1B1D</color>
23 <color name="yuzu_onSurface">#E2E2E6</color>
24 <color name="yuzu_surfaceVariant">#26282C</color>
25 <color name="yuzu_onSurfaceVariant">#C3C7CF</color>
26 <color name="yuzu_outline">#8C9199</color>
27 <color name="yuzu_inverseOnSurface">#1A1C1E</color>
28 <color name="yuzu_inverseSurface">#E2E2E6</color>
29 <color name="yuzu_inversePrimary">#0062A2</color>
30 <color name="yuzu_shadow">#000000</color>
31 <color name="yuzu_surfaceTint">#9DCAFF</color>
32 <color name="yuzu_outlineVariant">#42474E</color>
33
34 <color name="yuzu_ea_background_start">#840099</color>
35 <color name="yuzu_ea_background_end">#005AE1</color>
36
37</resources>
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 000000000..1cd1a8f87
--- /dev/null
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">To oprogramowanie umożliwia uruchomienie gier z konsoli Nintendo Switch. Nie zawiera gier ani wymaganych kluczy.&lt;br /&gt;&lt;br /&gt;Zanim zaczniesz, wybierz plik kluczy <![CDATA[<b> prod.keys </b>]]> z katalogu w pamięci masowej.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Dowiedz się więcej</a>]]></string>
5 <string name="emulation_notification_channel_name">Emulacja jest uruchomiona</string>
6 <string name="emulation_notification_channel_description">Pokaż trwałe powiadomienie gdy emulacja jest uruchomiona.</string>
7 <string name="emulation_notification_running">yuzu jest uruchomiony</string>
8 <string name="notice_notification_channel_name">Powiadomienia błędy</string>
9 <string name="notice_notification_channel_description">Pokaż powiadomienie gdy coś pójdzie źle</string>
10 <string name="notification_permission_not_granted">Nie zezwolono na powiadomienia!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Witaj!</string>
14 <string name="welcome_description">Zobacz jak skonfigurować &lt;b>yuzu&lt;/b> i wskocz w świat emulacji.</string>
15 <string name="get_started">Zaczynamy</string>
16 <string name="keys">Klucze</string>
17 <string name="keys_description">Wybierz swoje klucze &lt;b>prod.keys&lt;/b> za pomocą przycisku poniżej.</string>
18 <string name="select_keys">Wybierz klucze</string>
19 <string name="games">Gry</string>
20 <string name="games_description">Wybierz katalog z grami &lt;b>Games&lt;/b> za pomocą przycisku poniżej.</string>
21 <string name="done">Gotowe</string>
22 <string name="done_description">Wszystko skonfigurowane.\n Miłego grania!</string>
23 <string name="text_continue">Kontynuuj</string>
24 <string name="next">Dalej</string>
25 <string name="back">Wstecz</string>
26 <string name="add_games">Dodaj gry</string>
27 <string name="add_games_description">Wybierz folder zawierający Twoje gry</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Gry</string>
31 <string name="home_search">Szukaj</string>
32 <string name="home_settings">Ustawienia</string>
33 <string name="empty_gamelist">Nie znaleziono plików, lub nie wybrano jeszcze katalogu zawierającego gry.</string>
34 <string name="search_and_filter_games">Szukaj i filtruj gry</string>
35 <string name="select_games_folder">Wybierz folder z grami</string>
36 <string name="select_games_folder_description">Pozwala yuzu wygenerować listę gier</string>
37 <string name="add_games_warning">Pominąć wybór folderu z grami?</string>
38 <string name="add_games_warning_description">Aby pokazać listę gier wybierz katalog zawierający gry.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Szukaj gier</string>
41 <string name="games_dir_selected">Wybrano katalog gier</string>
42 <string name="install_prod_keys">Instaluj klucze prod.keys</string>
43 <string name="install_prod_keys_description">Wymagane aby poprawnie odczytać sklepowe gry</string>
44 <string name="install_prod_keys_warning">Pominąć dodawanie kluczy?</string>
45 <string name="install_prod_keys_warning_description">Poprawne klucze są wymagane aby emulować sklepowe gry. Jeśli przejdziesz dalej, jedynie homebrew będą działać.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Powiadomienia</string>
48 <string name="notifications_description">Nadaj uprawnienia dostępu do powiadomień. </string>
49 <string name="give_permission">Nadaj uprawnienia</string>
50 <string name="notification_warning">Pominąć nadanie uprawnień powiadomień?</string>
51 <string name="notification_warning_description">yuzu nie będzie mógł powiadamiać Cię o ważnych informacjach.</string>
52 <string name="permission_denied">Odmowa dostępu</string>
53 <string name="permission_denied_description">Odmówiłeś dostępu do powiadomień zbyt wiele razy, teraz musisz przyznać je w ustawieniach systemowych Androida.</string>
54 <string name="about">O aplikacji</string>
55 <string name="about_description">Wersja, podziękowania i więcej</string>
56 <string name="warning_help">Pomoc</string>
57 <string name="warning_skip">Pomiń</string>
58 <string name="warning_cancel">Anuluj</string>
59 <string name="install_amiibo_keys">Zainstaluj klucze Amiibo</string>
60 <string name="install_amiibo_keys_description">Wymagane aby korzystać z Amiibo w grze</string>
61 <string name="invalid_keys_file">Wybrano niepoprawne klucze</string>
62 <string name="install_keys_success">Klucze zainstalowane pomyślnie</string>
63 <string name="reading_keys_failure">Błąd podczas odczytu kluczy</string>
64 <string name="invalid_keys_error">Niepoprawne klucze</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">Wybrany plik jest niepoprawny lub uszkodzony. Zrzuć ponownie swoje klucze.</string>
67 <string name="install_gpu_driver">Zainstaluj sterownik GPU</string>
68 <string name="install_gpu_driver_description">Użyj alternatywnych sterowników aby potencjalnie zwiększyć wydajność i naprawić błędy</string>
69 <string name="advanced_settings">Ustawienia zaawansowane</string>
70 <string name="settings_description">Skonfiguruj ustawienia emulatora</string>
71 <string name="search_recently_played">Ostatnio grane</string>
72 <string name="search_recently_added">Ostatnio dodane</string>
73 <string name="search_retail">Sklepowe</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Otwórz folder yuzu</string>
76 <string name="open_user_folder_description">Zarządzaj plikami emulatora</string>
77 <string name="theme_and_color_description">Personalizuj wygląd aplikacji</string>
78 <string name="no_file_manager">Nie znaleziono menedżera plików</string>
79 <string name="notification_no_directory_link">Nie można otworzyć folderu emulatora</string>
80 <string name="notification_no_directory_link_description">Proszę wybrać ręcznie folder z pomocą panelu bocznego menedżera plików.</string>
81 <string name="manage_save_data">Zarządzaj plikami zapisów gier</string>
82 <string name="manage_save_data_description">Znaleziono pliki zapisów gier. Wybierz opcję poniżej.</string>
83 <string name="import_export_saves_description">Importuj lub wyeksportuj pliki zapisów</string>
84 <string name="import_export_saves_no_profile">Nie znaleziono plików zapisów. Uruchom grę i spróbuj ponownie.</string>
85 <string name="save_file_imported_success">Zaimportowano pomyślnie</string>
86 <string name="save_file_invalid_zip_structure">Niepoprawna struktura folderów</string>
87 <string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string>
88 <string name="import_saves">Importuj</string>
89 <string name="export_saves">Eksportuj</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia isn\'t real</string>
93 <string name="copied_to_clipboard">Skopiowano do schowka</string>
94 <string name="about_app_description">Otwarto-źródłowy emulator konsoli Switch</string>
95 <string name="contributors">Współtwórcy</string>
96 <string name="contributors_description">Stworzone z \u2764 przez zespół yuzu</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Wersja</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Wczesny dostęp</string>
105 <string name="get_early_access">Uzyskaj wczesny dostęp</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Nowe funkcje, szybszy dostęp do aktualizacji i nie tylko</string>
108 <string name="early_access_benefits">Korzyści z wcześniejszego dostępu</string>
109 <string name="cutting_edge_features">Nowatorskie funkcje</string>
110 <string name="early_access_updates">Częste aktualizacje</string>
111 <string name="no_manual_installation">Automatyczne aktualizacje</string>
112 <string name="prioritized_support">Priorytetowe wsparcie</string>
113 <string name="helping_game_preservation">Pomoc w problemach z grami</string>
114 <string name="our_eternal_gratitude">Nasza wdzięczność</string>
115 <string name="are_you_interested">Jesteś zainteresowany?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Włącz limit szybkości emulacji</string>
119 <string name="frame_limit_enable_description">Włącz, aby ustawić procentowy limit szybkości emulacji</string>
120 <string name="frame_limit_slider">Procentowy limit szybkości emulacji</string>
121 <string name="frame_limit_slider_description">Określa limit szybkości emulacji gier. Domyślna wartość 100% oznacza normalną szybkość z jaką działa gra. Wartości niższe lub wyższe zmniejszą lub zwiększą limit szybkości.</string>
122 <string name="cpu_accuracy">Dokładność procesora CPU</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Tryb zadokowany</string>
126 <string name="use_docked_mode_description">Emulacja w trybie stacji dokującej, zwiększa rozdzielczość kosztem wydajności.</string>
127 <string name="emulated_region">Region emulacji</string>
128 <string name="emulated_language">Język emulacji</string>
129 <string name="select_rtc_date">Ustaw datę RTC</string>
130 <string name="select_rtc_time">Ustaw czas RTC</string>
131 <string name="use_custom_rtc">Włącz niestandardowy zegar RTC</string>
132 <string name="use_custom_rtc_description">Ta opcja pozwala na wybranie własnych ustawień czasu używanych w czasie emulacji, innych niż czas systemu Android.</string>
133 <string name="set_custom_rtc">Ustaw niestandardowy czas RTC</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">Interfejs graficzny</string>
137 <string name="renderer_accuracy">Poziom precyzji emulacji</string>
138 <string name="renderer_resolution">Rozdzielczość</string>
139 <string name="renderer_vsync">Synchronizacja pionowa VSync</string>
140 <string name="renderer_aspect_ratio">Proporcje ekranu</string>
141 <string name="renderer_scaling_filter">Filtr adaptacji rozdzielczości</string>
142 <string name="renderer_anti_aliasing">Metoda wygładzania krawędzi</string>
143 <string name="renderer_force_max_clock">Maksymalne taktowanie GPU (układy Adreno)</string>
144 <string name="renderer_force_max_clock_description">Wymusza uruchomienie maksymalnego taktowania układu graficznego (zabezpieczenia termiczne będą dalej aktywne).</string>
145 <string name="renderer_asynchronous_shaders">Wyłącz synchronizację shaderów</string>
146 <string name="renderer_asynchronous_shaders_description">Kompiluj oświetlenie bez synchronizacji, poprawi wydajność ale może powodować błędy.</string>
147 <string name="renderer_debug">Włącz debugowanie grafiki</string>
148 <string name="renderer_debug_description">Kiedy włączone, interfejs graficzny korzysta z wolnego trybu debugowania błędów.</string>
149 <string name="use_disk_shader_cache">Użyj pamięci podręcznej shaderów na dysku</string>
150 <string name="use_disk_shader_cache_description">Zmniejsza przycięcia przez przechowywanie gotowych wygenerowanych plików oświetlenia w pamięci urządzenia.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Głośność</string>
154 <string name="audio_volume_description">Ustala poziom głośności wyjścia dźwięku.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">Domyślne</string>
158 <string name="ini_saved">Ustawienia zapisane</string>
159 <string name="gameid_saved">Ustawienia zapisane w %1$s</string>
160 <string name="error_saving">Błąd zapisu %1$s.ini: %2$s</string>
161 <string name="loading">Wczytywanie...</string>
162 <string name="reset_setting_confirmation">Przywrócić wartość tego ustawienia do wartości domyślnej?</string>
163 <string name="reset_to_default">Przywróć ustawienia domyślne</string>
164 <string name="reset_all_settings">Przywrócić WSZYSTKIE ustawienia?</string>
165 <string name="reset_all_settings_description">Wszystkie zaawansowane opcje zostaną przywrócone do wartości domyślnych. Czynności nie będzie można cofnąć.</string>
166 <string name="settings_reset">Reset ustawień</string>
167 <string name="close">Zamknij</string>
168 <string name="learn_more">Dowiedz się więcej</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Wybierz sterownik GPU </string>
172 <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
173 <string name="select_gpu_driver_install">Zainstaluj</string>
174 <string name="select_gpu_driver_default">Domyślne</string>
175 <string name="select_gpu_driver_install_success">Zainstalowano %s</string>
176 <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
177 <string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string>
178 <string name="system_gpu_driver">Systemowy sterownik GPU</string>
179 <string name="installing_driver">Instalowanie sterownika...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Ustawienia</string>
183 <string name="preferences_general">Ogólne</string>
184 <string name="preferences_system">System</string>
185 <string name="preferences_graphics">Grafika</string>
186 <string name="preferences_audio">Dźwięk</string>
187 <string name="preferences_theme">Motyw i kolor</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">Twój ROM jest zakodowany</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Użyj przewodnika aby wykonać zrzuty <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">kardridży</a> lub <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">zainstalowanych gier</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Upewnij się że plik kluczy <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> jest zainstalowany aby gry mogły zostać odczytane.]]></string>
193 <string name="loader_error_video_core">Błąd inicjacji podsystemu graficznego</string>
194 <string name="loader_error_video_core_description">Zazwyczaj spowodowane niekompatybilnym sterownikiem GPU, instalacja niestandardowego sterownika może rozwiązać ten problem.</string>
195 <string name="loader_error_invalid_format">Nie można wczytać pliku ROM</string>
196 <string name="loader_error_file_not_found">Plik ROM nie istnieje</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Zakończ emulację</string>
200 <string name="emulation_done">Gotowe</string>
201 <string name="emulation_fps_counter">Licznik FPS</string>
202 <string name="emulation_toggle_controls">Wybierz przyciski</string>
203 <string name="emulation_rel_stick_center">Wycentruj gałki</string>
204 <string name="emulation_dpad_slide">Ruchomy DPad</string>
205 <string name="emulation_haptics">Wibracje haptyczne</string>
206 <string name="emulation_show_overlay">Pokaż przyciski</string>
207 <string name="emulation_toggle_all">Zaznacz wszystkie</string>
208 <string name="emulation_control_adjust">Dostosuj nakładkę</string>
209 <string name="emulation_control_scale">Skala</string>
210 <string name="emulation_control_opacity">Przeźroczystość</string>
211 <string name="emulation_touch_overlay_reset">Resetuj</string>
212 <string name="emulation_touch_overlay_edit">Edytuj nakładkę</string>
213 <string name="emulation_pause">Wstrzymaj emulację</string>
214 <string name="emulation_unpause">Wznów emulację</string>
215 <string name="emulation_input_overlay">Opcje nakładki</string>
216 <string name="emulation_game_loading">Wczytywanie gry...</string>
217
218 <string name="load_settings">Wczytywanie ustawień...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Klawiatura systemowa</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Przerwij</string>
225 <string name="continue_button">Kontynuuj</string>
226 <string name="system_archive_not_found">Archiwum systemu nie znalezione.</string>
227 <string name="system_archive_not_found_message">%s nieznaleziony. Proszę wykonać zrzut archiwum systemu.\nKontynuowanie może powodować błędy lub przerwanie emulacji.</string>
228 <string name="system_archive_general">Archiwum systemu</string>
229 <string name="save_load_error">Błąd odczytu/zapisu</string>
230 <string name="fatal_error">Błąd krytyczny</string>
231 <string name="fatal_error_message">Wystąpił błąd krytyczny. Szczegóły znajdziesz w pliku log.\nKontynuowanie może spowodować błędy lub przerwanie emulacji. </string>
232 <string name="performance_warning">Wyłączenie tej opcji znacząco ograniczy wydajność! Dla najlepszego doświadczenia, zaleca się zostawienie tej opcji włączonej.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Japonia</string>
236 <string name="region_usa">USA</string>
237 <string name="region_europe">Europa</string>
238 <string name="region_australia">Australia</string>
239 <string name="region_china">Chiny</string>
240 <string name="region_korea">Korea</string>
241 <string name="region_taiwan">Tajwan</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Japoński (日本語)</string>
245 <string name="language_english">Angielski</string>
246 <string name="language_french">Francuski (Francja)</string>
247 <string name="langauge_german">Niemiecki (Niemcy)</string>
248 <string name="language_italian">Włoski (Włochy)</string>
249 <string name="language_spanish">Hiszpański (Hiszpania)</string>
250 <string name="language_chinese">Chiński (简体中文)</string>
251 <string name="language_korean">Koreański (한국어)</string>
252 <string name="language_dutch">Duński (Holandia)</string>
253 <string name="language_portuguese">Portugalski (Portugalia)</string>
254 <string name="language_russian">Rosyjski (Русский)</string>
255 <string name="language_taiwanese">Tajwański (台湾)</string>
256 <string name="language_british_english">Angielski Brytyjski</string>
257 <string name="language_canadian_french">Francuski (Kanada)</string>
258 <string name="language_latin_american_spanish">Hiszpański (Ameryka Latynoska)</string>
259 <string name="language_simplified_chinese">Chiński uproszczony (简体中文)</string>
260 <string name="language_traditional_chinese">Chiński tradycyjny (正體中文)</string>
261 <string name="language_brazilian_portuguese">Portugalski (Brazylia)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">Żadny</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Normalny</string>
269 <string name="renderer_accuracy_high">Wysoki</string>
270 <string name="renderer_accuracy_extreme">Ekstremalny (Wolny)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Wolno)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Wolno)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Wolno)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Natychmiastowa (Wyłączona)</string>
282 <string name="renderer_vsync_mailbox">Skrzynka pocztowa</string>
283 <string name="renderer_vsync_fifo">FIFO (Włączona)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO Relaks</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Najbliższy sąsiadujący</string>
288 <string name="scaling_filter_bilinear">Bilinearny</string>
289 <string name="scaling_filter_bicubic">Bikubiczny</string>
290 <string name="scaling_filter_gaussian">Kulisty</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Żadna (wyłączony)</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">Domyślne (16:9)</string>
301 <string name="ratio_force_four_three">Wymuś 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Wymuś 21:9</string>
303 <string name="ratio_force_sixteen_ten">Wymuś 16:10</string>
304 <string name="ratio_stretch">Rozciągnij do Okna</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Dokładny</string>
308 <string name="cpu_accuracy_unsafe">Niebezpieczny</string>
309 <string name="cpu_accuracy_paranoid">Paranoid (Wolny)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">D-Pad</string>
313 <string name="gamepad_left_stick">Lewa gałka</string>
314 <string name="gamepad_right_stick">Prawa gałka</string>
315 <string name="gamepad_home">Home</string>
316 <string name="gamepad_screenshot">Zrzut ekranu</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">Przygotowanie shaderów</string>
320 <string name="building_shaders">Budowanie shaderów</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Zmień motyw aplikacji</string>
324 <string name="theme_default">Domyślny</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Zmiana trybu motywu</string>
329 <string name="theme_mode_follow_system">Podążaj za systemowym</string>
330 <string name="theme_mode_light">Jasny</string>
331 <string name="theme_mode_dark">Ciemny</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Używaj czarnego tła</string>
335 <string name="use_black_backgrounds_description">Kiedy używany ciemny motyw, tła zostają zastąpione czernią.</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..35197c280
--- /dev/null
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Este software corre jogos para a consola Nintendo Switch. Não estão incluídas nem jogos ou chaves. &lt;br /&gt;&lt;br /&gt;Antes de começares, por favor localiza o ficheiro <![CDATA[1 prod.keys 1]]> no armazenamento do teu dispositivo.&lt;br /&gt;&lt;br /&gt;<![CDATA[2Learn more2]]></string>
5 <string name="emulation_notification_channel_name">Emulação está Ativa</string>
6 <string name="emulation_notification_channel_description">Mostra uma notificação permanente enquanto a emulação está a correr.</string>
7 <string name="emulation_notification_running">Yuzu está em execução </string>
8 <string name="notice_notification_channel_name">Notificações e erros</string>
9 <string name="notice_notification_channel_description">Mostra notificações quendo algo corre mal.</string>
10 <string name="notification_permission_not_granted">Permissões de notificação não permitidas </string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Bemvindo! </string>
14 <string name="welcome_description">Aprende como configurar &lt;b>yuzu&lt;/b> e arranca a emulação.</string>
15 <string name="get_started">Começa</string>
16 <string name="keys">Chaves</string>
17 <string name="keys_description">Seleciona o teu ficheiro &lt;b>prod.keys&lt;/b> com o botão abaixo.</string>
18 <string name="select_keys">Seleciona as Chaves</string>
19 <string name="games">Jogos</string>
20 <string name="games_description">Seleciona a tua pasta &lt;b>Games&lt;/b> com o botão abaixo.</string>
21 <string name="done">Feito</string>
22 <string name="done_description">Tudo pronto.\nDisfruta dos teus jogos!</string>
23 <string name="text_continue">Continuar</string>
24 <string name="next">Próximo</string>
25 <string name="back">Voltar</string>
26 <string name="add_games">Adiciona Jogos</string>
27 <string name="add_games_description">Seleciona a tua pasta de Jogos</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Jogos</string>
31 <string name="home_search">Pesquisar</string>
32 <string name="home_settings">Configurações</string>
33 <string name="empty_gamelist">Não foram encontrados jogos ou a pasta de Jogos ainda não foi definida. </string>
34 <string name="search_and_filter_games">Procura e filtra jogos.</string>
35 <string name="select_games_folder">Seleciona a pasta de jogos.</string>
36 <string name="select_games_folder_description">Permite que o Yuzu preencha a lista de jogos</string>
37 <string name="add_games_warning">Ignorar a seleção da pasta de jogos?</string>
38 <string name="add_games_warning_description">Os jogos não serão exibidos na lista de jogos se uma pasta não estiver selecionada.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Procurar Jogos</string>
41 <string name="games_dir_selected">Pasta de Jogos selecionada</string>
42 <string name="install_prod_keys">Instala prod.keys</string>
43 <string name="install_prod_keys_description">Necessário para desencriptar jogos comerciais</string>
44 <string name="install_prod_keys_warning">Ignorar a adição de chaves?</string>
45 <string name="install_prod_keys_warning_description">São necessárias chaves válidas para emular jogos comerciais. Somente aplicativos homebrew funcionarão se você continuar.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#Guia de introdução</string>
47 <string name="notifications">Notificações</string>
48 <string name="notifications_description">Conceda a permissão de notificação com o botão abaixo.</string>
49 <string name="give_permission">Conceda permissão</string>
50 <string name="notification_warning">Saltar a concessão da permissão de notificação?</string>
51 <string name="notification_warning_description">Yuzu não conseguirá te notificar de informações importantes. </string>
52 <string name="permission_denied">Permissão negada</string>
53 <string name="permission_denied_description">Você negou essa permissão muitas vezes e agora precisa concedê-la manualmente nas configurações do sistema.</string>
54 <string name="about">Sobre</string>
55 <string name="about_description">Versão de compilação, créditos e mais</string>
56 <string name="warning_help">Ajuda</string>
57 <string name="warning_skip">Saltar</string>
58 <string name="warning_cancel">Cancelar</string>
59 <string name="install_amiibo_keys">Instala chaves Amiibo</string>
60 <string name="install_amiibo_keys_description">Necessário para usares Amiibo no jogo</string>
61 <string name="invalid_keys_file">Ficheiro de chaves inválido</string>
62 <string name="install_keys_success">Chaves instaladas com sucesso</string>
63 <string name="reading_keys_failure">Erro ao ler chaves de encriptação</string>
64 <string name="invalid_keys_error">Chaves de encriptação inválidas</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">O ficheiro selecionado está corrompido. Por favor recarrega as tuas chaves.</string>
67 <string name="install_gpu_driver">Instala driver para GPU</string>
68 <string name="install_gpu_driver_description">Instala drivers alternativos para desempenho ou precisão potencialmente melhores</string>
69 <string name="advanced_settings">Definições avançadas</string>
70 <string name="settings_description">Configura definições do emulador</string>
71 <string name="search_recently_played">Jogos recentes</string>
72 <string name="search_recently_added">Adicionados recentemente</string>
73 <string name="search_retail">Jogos comerciais</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Abre a pasta Yuzu</string>
76 <string name="open_user_folder_description">Gere os ficheiro internos do Yuzu</string>
77 <string name="theme_and_color_description">Modifica a aparência da App</string>
78 <string name="no_file_manager">Nenhum gestor de ficheiros encontrado</string>
79 <string name="notification_no_directory_link">Impossível abrir pasta Yuzu</string>
80 <string name="notification_no_directory_link_description">Localiza a pasta de utilizador manualmente com o painel lateral do gestor de ficheiros.</string>
81 <string name="manage_save_data">Gerir dados guardados</string>
82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string>
83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string>
84 <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string>
85 <string name="save_file_imported_success">Importado com sucesso</string>
86 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string>
87 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
88 <string name="import_saves">Importar</string>
89 <string name="export_saves">Exportar</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia não é real</string>
93 <string name="copied_to_clipboard">Copiado para a área de transferência</string>
94 <string name="about_app_description">Um emulador Switch de código aberto</string>
95 <string name="contributors">Contribuidores</string>
96 <string name="contributors_description">Feito com \u2764 da equipa do Yuzu</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Versão</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Acesso antecipado</string>
105 <string name="get_early_access">Obtém Acesso Antecipado</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Recursos de ponta, acesso antecipado a atualizações e muito mais</string>
108 <string name="early_access_benefits">Benefícios do Acesso Antecipado</string>
109 <string name="cutting_edge_features">Recursos de ponta</string>
110 <string name="early_access_updates">Acesso antecipado a atualizações</string>
111 <string name="no_manual_installation">Sem instalação manual</string>
112 <string name="prioritized_support">Suporte prioritário</string>
113 <string name="helping_game_preservation">Ajuda na preservação dos jogos</string>
114 <string name="our_eternal_gratitude">A nossa eterna gratidão</string>
115 <string name="are_you_interested">Estás interessado?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Ativar limite de velocidade</string>
119 <string name="frame_limit_enable_description">Quando ativada, a velocidade da emulação será limitada à percentagem definida da velocidade normal.</string>
120 <string name="frame_limit_slider">Percentagem do limite de velocidade</string>
121 <string name="frame_limit_slider_description">Especifica o limite da percentagem da velocidade da emulação. Com a velocidade por defeito a 100% a emulação será limitada à velocidade normal. Valores maiores ou menores aumentarão ou diminuirão o limite de velocidade.</string>
122 <string name="cpu_accuracy">Precisão do CPU</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Modo ancorado</string>
126 <string name="use_docked_mode_description">Emula em modo ancorado, que aumenta a resolução ás custas da performance.</string>
127 <string name="emulated_region">Região da emulação</string>
128 <string name="emulated_language">Idioma da emulação</string>
129 <string name="select_rtc_date">Seleciona a data RTC</string>
130 <string name="select_rtc_time">Seleciona a hora RTC</string>
131 <string name="use_custom_rtc">Ativa RTC personalizado</string>
132 <string name="use_custom_rtc_description">Esta configuração permite definir um RTC personalizado diferente da hora atual do sistema</string>
133 <string name="set_custom_rtc">Define RTC personalizado</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">Nível de precisão</string>
138 <string name="renderer_resolution">Resolução</string>
139 <string name="renderer_vsync">Modo VSync</string>
140 <string name="renderer_aspect_ratio">Proporção do ecrã</string>
141 <string name="renderer_scaling_filter">Filtro de Adaptação da Janela</string>
142 <string name="renderer_anti_aliasing">Método de Anti-Aliasing </string>
143 <string name="renderer_force_max_clock">Força velocidade máxima (Adreno only)</string>
144 <string name="renderer_force_max_clock_description">Força o GPU a correr à velocidade máxima (restrições térmicas serão aplicadas)</string>
145 <string name="renderer_asynchronous_shaders">Usa shaders assíncronos </string>
146 <string name="renderer_asynchronous_shaders_description">Compila shaders assincronamente, que aumentará a fluidez, mas poderá causar falhas.</string>
147 <string name="renderer_debug">Ativar depuração de gráficos</string>
148 <string name="renderer_debug_description">Quando selecionado, a API gráfica entra num modo de depuração mais lento.</string>
149 <string name="use_disk_shader_cache">Usar cache de shaders em disco</string>
150 <string name="use_disk_shader_cache_description">Aumenta a fluidez ao guardar e carregar shaders gerados para o armazenamento.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Volume</string>
154 <string name="audio_volume_description">Especifica o volume de saída.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">Padrão</string>
158 <string name="ini_saved">Definições guardadas</string>
159 <string name="gameid_saved">Definições guardadas para %1$s</string>
160 <string name="error_saving">Erro ao guardar %1$s.ini: %2$s</string>
161 <string name="loading">A carregar...</string>
162 <string name="reset_setting_confirmation">Queres reverter esta definição para os valores padrão?</string>
163 <string name="reset_to_default">Reverter para padrão</string>
164 <string name="reset_all_settings">Redefinir todas as definições?</string>
165 <string name="reset_all_settings_description">Todas as definições avançadas serão redefinidas para as definições padrão. Isto não pode ser revertido.</string>
166 <string name="settings_reset">Redefinir definições</string>
167 <string name="close">Fechar</string>
168 <string name="learn_more">Saiba mais</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Seleciona a driver para o GPU</string>
172 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
173 <string name="select_gpu_driver_install">Instalar</string>
174 <string name="select_gpu_driver_default">Padrão</string>
175 <string name="select_gpu_driver_install_success">Instalado%s</string>
176 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
177 <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
178 <string name="system_gpu_driver">Driver do GPU padrão</string>
179 <string name="installing_driver">A instalar o Driver...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Configurações</string>
183 <string name="preferences_general">Geral</string>
184 <string name="preferences_system">Sistema</string>
185 <string name="preferences_graphics">Gráficos</string>
186 <string name="preferences_audio">Áudio</string>
187 <string name="preferences_theme">Cor e tema.</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">A tua ROM está encriptada</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor segue os guias para fazer redump das tuas<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">Cartidges de Jogo</a> or <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">Jogos Instalados</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Por favor confirma que o teu ficheiro <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> está instalado para que os jogos possam ser desencriptados.]]></string>
193 <string name="loader_error_video_core">Ocorreu um erro ao iniciar o núcleo de vídeo.</string>
194 <string name="loader_error_video_core_description">Isto é normalmente causado por um driver de GPU incompatível. Instalar um driver GPU pode resolver este problema.</string>
195 <string name="loader_error_invalid_format">Impossível carregar a tua ROM</string>
196 <string name="loader_error_file_not_found">O ficheiro da ROM não existe</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Sair da emulação</string>
200 <string name="emulation_done">Feito</string>
201 <string name="emulation_fps_counter">Contador de FPS</string>
202 <string name="emulation_toggle_controls">Alterar Controlos</string>
203 <string name="emulation_rel_stick_center">Centro do Analógico Relativo</string>
204 <string name="emulation_dpad_slide">Deslizar do DPad</string>
205 <string name="emulation_haptics">Hápticos </string>
206 <string name="emulation_show_overlay">Mostrar sobreposição </string>
207 <string name="emulation_toggle_all">Alterar todos</string>
208 <string name="emulation_control_adjust">Ajustar a sobreposição </string>
209 <string name="emulation_control_scale">Escala</string>
210 <string name="emulation_control_opacity">Opacidade</string>
211 <string name="emulation_touch_overlay_reset">Redefinir Sobreposição </string>
212 <string name="emulation_touch_overlay_edit">Editar sobreposição </string>
213 <string name="emulation_pause">Pausa emulação</string>
214 <string name="emulation_unpause">Retomar emulação</string>
215 <string name="emulation_input_overlay">Opções de sobreposição </string>
216 <string name="emulation_game_loading">Jogo a carregar...</string>
217
218 <string name="load_settings">Configurações a carregar...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Teclado de software</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Abortar</string>
225 <string name="continue_button">Continuar</string>
226 <string name="system_archive_not_found">Arquivo do sistema não encontrado</string>
227 <string name="system_archive_not_found_message">%s está em falta. Por favor apaga os teus ficheiros de sistema.\nContinuar a emulação pode causar erros.</string>
228 <string name="system_archive_general">Um arquivo do sistema</string>
229 <string name="save_load_error">Erro Guardar/Carregar</string>
230 <string name="fatal_error">Erro fatal</string>
231 <string name="fatal_error_message">Ocorreu um erro fatal. Verifica o teu registro para detalhes. \nContinuar a emulação pode causar erros.</string>
232 <string name="performance_warning">Desligar esta configuração irá reduzir a performance da emulação significantemente! Para a melhor experiência é recomendado que deixes esta configuração ativada.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Japão</string>
236 <string name="region_usa">EUA</string>
237 <string name="region_europe">Europa</string>
238 <string name="region_australia">Austrália</string>
239 <string name="region_china">China</string>
240 <string name="region_korea">Coréia</string>
241 <string name="region_taiwan">Taiwan</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Japônes (日本語)</string>
245 <string name="language_english">Português do Brasil</string>
246 <string name="language_french">Francês (Français)</string>
247 <string name="langauge_german">Alemão (Deutsch)</string>
248 <string name="language_italian">Italiano (Italiano)</string>
249 <string name="language_spanish">Espanhol (Español)</string>
250 <string name="language_chinese">Mandarim (简体中文)</string>
251 <string name="language_korean">Coreano (한국어)</string>
252 <string name="language_dutch">Holandês (Nederlands)</string>
253 <string name="language_portuguese">Português (Português)</string>
254 <string name="language_russian">Russo (Русский)</string>
255 <string name="language_taiwanese">Taiwanês (台湾)</string>
256 <string name="language_british_english">Inglês britânico (British English)</string>
257 <string name="language_canadian_french">Fracês Canadiano (Français canadien)</string>
258 <string name="language_latin_american_spanish">Espanhol da América Latina (Español latino-americano)</string>
259 <string name="language_simplified_chinese">Chinês Simplificado (简体中文)</string>
260 <string name="language_traditional_chinese">Chinês tradicional (正體中文)</string>
261 <string name="language_brazilian_portuguese">Português do Brasil (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulcano</string>
265 <string name="renderer_none">Nenhum</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Normal</string>
269 <string name="renderer_accuracy_high">Alto</string>
270 <string name="renderer_accuracy_extreme">Estremo (Lento)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Slow)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Lento)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Lento)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Imediato (Desligado)</string>
282 <string name="renderer_vsync_mailbox">Caixa de entrada</string>
283 <string name="renderer_vsync_fifo">FIFO (Ligado)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO Relaxado </string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Vizinho mais próximo</string>
288 <string name="scaling_filter_bilinear">Bilinear</string>
289 <string name="scaling_filter_bicubic">Bicúbico</string>
290 <string name="scaling_filter_gaussian">Gaussiano</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Nenhum</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">Padrão (16:9)</string>
301 <string name="ratio_force_four_three">Forçar 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Forçar 21:9</string>
303 <string name="ratio_force_sixteen_ten">Forçar 16:10</string>
304 <string name="ratio_stretch">Esticar para a janela</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Preciso</string>
308 <string name="cpu_accuracy_unsafe">Não seguro</string>
309 <string name="cpu_accuracy_paranoid">Paranoid (Lento)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">D-pad</string>
313 <string name="gamepad_left_stick">Analógico esquerdo</string>
314 <string name="gamepad_right_stick">Analógico direito</string>
315 <string name="gamepad_home">Botão Home</string>
316 <string name="gamepad_screenshot">Captura de ecrã</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">A preparar shaders</string>
320 <string name="building_shaders">A criar shaders</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Muda o Tema da App</string>
324 <string name="theme_default">Padrão</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Altera o Modo do Tema</string>
329 <string name="theme_mode_follow_system">Igual ao Sistema</string>
330 <string name="theme_mode_light">Claro</string>
331 <string name="theme_mode_dark">Escuro</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Usa Fundos Negros</string>
335 <string name="use_black_backgrounds_description">Quando usar tema escuro, aplicar fundos escuros</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 000000000..8761e2374
--- /dev/null
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Este software corre jogos para a consola Nintendo Switch. Não estão incluídas nem jogos ou chaves. &lt;br /&gt;&lt;br /&gt;Antes de começares, por favor localiza o ficheiro <![CDATA[1 prod.keys 1]]> no armazenamento do teu dispositivo.&lt;br /&gt;&lt;br /&gt;<![CDATA[2Learn more2]]></string>
5 <string name="emulation_notification_channel_name">Emulação está Ativa</string>
6 <string name="emulation_notification_channel_description">Mostra uma notificação permanente enquanto a emulação está a correr.</string>
7 <string name="emulation_notification_running">Yuzu está em execução </string>
8 <string name="notice_notification_channel_name">Notificações e erros</string>
9 <string name="notice_notification_channel_description">Mostra notificações quendo algo corre mal.</string>
10 <string name="notification_permission_not_granted">Permissões de notificação não permitidas </string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Benvindo! </string>
14 <string name="welcome_description">Aprende como configurar &lt;b>yuzu&lt;/b> e arranca a emulação.</string>
15 <string name="get_started">Começa</string>
16 <string name="keys">Chaves</string>
17 <string name="keys_description">Seleciona o teu ficheiro &lt;b>prod.keys&lt;/b> com o botão abaixo.</string>
18 <string name="select_keys">Seleciona as Chaves</string>
19 <string name="games">Jogos</string>
20 <string name="games_description">Seleciona a tua pasta &lt;b>Games&lt;/b> com o botão abaixo.</string>
21 <string name="done">Feito</string>
22 <string name="done_description">Tudo pronto.\nDisfruta dos teus jogos!</string>
23 <string name="text_continue">Continuar</string>
24 <string name="next">Próximo</string>
25 <string name="back">Voltar</string>
26 <string name="add_games">Adiciona Jogos</string>
27 <string name="add_games_description">Seleciona a tua pasta de Jogos</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Jogos</string>
31 <string name="home_search">Pesquisar</string>
32 <string name="home_settings">Configurações</string>
33 <string name="empty_gamelist">Não foram encontrados jogos ou a pasta de Jogos ainda não foi definida. </string>
34 <string name="search_and_filter_games">Procura e filtra jogos.</string>
35 <string name="select_games_folder">Seleciona a pasta de jogos.</string>
36 <string name="select_games_folder_description">Permite que o Yuzu preencha a lista de jogos</string>
37 <string name="add_games_warning">Ignorar a seleção da pasta de jogos?</string>
38 <string name="add_games_warning_description">Os jogos não serão exibidos na lista de jogos se uma pasta não estiver selecionada.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Procurar Jogos</string>
41 <string name="games_dir_selected">Pasta de Jogos selecionada</string>
42 <string name="install_prod_keys">Instala prod.keys</string>
43 <string name="install_prod_keys_description">Necessário para desencriptar jogos comerciais</string>
44 <string name="install_prod_keys_warning">Ignorar a adição de chaves?</string>
45 <string name="install_prod_keys_warning_description">São necessárias chaves válidas para emular jogos comerciais. Somente aplicativos homebrew funcionarão se você continuar.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Notificações</string>
48 <string name="notifications_description">Conceda a permissão de notificação com o botão abaixo.</string>
49 <string name="give_permission">Conceda permissão</string>
50 <string name="notification_warning">Saltar a concessão da permissão de notificação?</string>
51 <string name="notification_warning_description">Yuzu não conseguirá te notificar de informações importantes. </string>
52 <string name="permission_denied">Permissão negada</string>
53 <string name="permission_denied_description">Você negou essa permissão muitas vezes e agora precisa concedê-la manualmente nas configurações do sistema.</string>
54 <string name="about">Sobre</string>
55 <string name="about_description">Versão de compilação, créditos e mais</string>
56 <string name="warning_help">Ajuda</string>
57 <string name="warning_skip">Saltar</string>
58 <string name="warning_cancel">Cancelar</string>
59 <string name="install_amiibo_keys">Instala chaves Amiibo</string>
60 <string name="install_amiibo_keys_description">Necessário para usares Amiibo no jogo</string>
61 <string name="invalid_keys_file">Ficheiro de chaves inválido</string>
62 <string name="install_keys_success">Chaves instaladas com sucesso</string>
63 <string name="reading_keys_failure">Erro ao ler chaves de encriptação</string>
64 <string name="invalid_keys_error">Chaves de encriptação inválidas</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">O ficheiro selecionado está corrompido. Por favor recarrega as tuas chaves.</string>
67 <string name="install_gpu_driver">Instala driver para GPU</string>
68 <string name="install_gpu_driver_description">Instala drivers alternativos para desempenho ou precisão potencialmente melhores</string>
69 <string name="advanced_settings">Configurações avançadas</string>
70 <string name="settings_description">Configura configurações do emulador</string>
71 <string name="search_recently_played">Jogos recentes</string>
72 <string name="search_recently_added">Adicionados recentemente</string>
73 <string name="search_retail">Jogos comerciais</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Abre a pasta Yuzu</string>
76 <string name="open_user_folder_description">Gere os ficheiro internos do Yuzu</string>
77 <string name="theme_and_color_description">Modifica a aparência da App</string>
78 <string name="no_file_manager">Nenhum gestor de ficheiros encontrado</string>
79 <string name="notification_no_directory_link">Impossível abrir pasta Yuzu</string>
80 <string name="notification_no_directory_link_description">Localiza a pasta de utilizador manualmente com o painel lateral do gestor de ficheiros.</string>
81 <string name="manage_save_data">Gerir dados guardados</string>
82 <string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string>
83 <string name="import_export_saves_description">Importa ou exporta dados guardados</string>
84 <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string>
85 <string name="save_file_imported_success">Importado com sucesso</string>
86 <string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string>
87 <string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
88 <string name="import_saves">Importar</string>
89 <string name="export_saves">Exportar</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia não é real</string>
93 <string name="copied_to_clipboard">Copiado para a área de transferência</string>
94 <string name="about_app_description">Um emulador Switch de código aberto</string>
95 <string name="contributors">Contribuidores</string>
96 <string name="contributors_description">Feito com \u2764 da equipa do Yuzu</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Versão</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Acesso antecipado</string>
105 <string name="get_early_access">Obtém Acesso Antecipado</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Recursos de ponta, acesso antecipado a atualizações e muito mais</string>
108 <string name="early_access_benefits">Benefícios do Acesso Antecipado</string>
109 <string name="cutting_edge_features">Recursos de ponta</string>
110 <string name="early_access_updates">Acesso antecipado a atualizações</string>
111 <string name="no_manual_installation">Sem instalação manual</string>
112 <string name="prioritized_support">Suporte prioritário</string>
113 <string name="helping_game_preservation">Ajuda na preservação dos jogos</string>
114 <string name="our_eternal_gratitude">A nossa eterna gratidão</string>
115 <string name="are_you_interested">Estás interessado?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Ativar limite de velocidade</string>
119 <string name="frame_limit_enable_description">Quando ativada, a velocidade da emulação será limitada à percentagem definida da velocidade normal.</string>
120 <string name="frame_limit_slider">Percentagem do limite de velocidade</string>
121 <string name="frame_limit_slider_description">Especifica o limite da percentagem da velocidade da emulação. Com a velocidade por defeito a 100% a emulação será limitada à velocidade normal. Valores maiores ou menores aumentarão ou diminuirão o limite de velocidade.</string>
122 <string name="cpu_accuracy">Precisão do CPU</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Modo ancorado</string>
126 <string name="use_docked_mode_description">Emula em modo ancorado, que aumenta a resolução ás custas da performance.</string>
127 <string name="emulated_region">Região da emulação</string>
128 <string name="emulated_language">Idioma da emulação</string>
129 <string name="select_rtc_date">Seleciona a data RTC</string>
130 <string name="select_rtc_time">Seleciona a hora RTC</string>
131 <string name="use_custom_rtc">Ativa RTC personalizado</string>
132 <string name="use_custom_rtc_description">Esta configuração permite definir um RTC personalizado diferente da hora atual do sistema</string>
133 <string name="set_custom_rtc">Define RTC personalizado</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">Nível de precisão</string>
138 <string name="renderer_resolution">Resolução</string>
139 <string name="renderer_vsync">Modo VSync</string>
140 <string name="renderer_aspect_ratio">Proporção do ecrã</string>
141 <string name="renderer_scaling_filter">Filtro de Adaptação da Janela</string>
142 <string name="renderer_anti_aliasing">Método de Anti-Aliasing </string>
143 <string name="renderer_force_max_clock">Força velocidade máxima (Adreno only)</string>
144 <string name="renderer_force_max_clock_description">Força o GPU a correr à velocidade máxima (restrições térmicas serão aplicadas)</string>
145 <string name="renderer_asynchronous_shaders">Usa shaders assíncronos </string>
146 <string name="renderer_asynchronous_shaders_description">Compila shaders assincronamente, que aumentará a fluidez, mas poderá causar falhas.</string>
147 <string name="renderer_debug">Ativar depuração de gráficos</string>
148 <string name="renderer_debug_description">Quando selecionado, a API gráfica entra num modo de depuração mais lento.</string>
149 <string name="use_disk_shader_cache">Usar cache do disk shader</string>
150 <string name="use_disk_shader_cache_description">Aumenta a fluidez ao guardar e carregar shaders gerados para o armazenamento.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Volume</string>
154 <string name="audio_volume_description">Especifica o volume de saída.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">Padrão</string>
158 <string name="ini_saved">Configurações guardadas</string>
159 <string name="gameid_saved">Configurações guardadas para %1$s</string>
160 <string name="error_saving">Erro ao guardar %1$s.ini: %2$s</string>
161 <string name="loading">A carregar...</string>
162 <string name="reset_setting_confirmation">Queres reverter esta definição para os valores padrão?</string>
163 <string name="reset_to_default">Reverter para padrão</string>
164 <string name="reset_all_settings">Redefinir todas as configurações?</string>
165 <string name="reset_all_settings_description">Todas as configurações avançadas serão redefinidas para as definições padrão. Isto não pode ser revertido.</string>
166 <string name="settings_reset">Redefinir configurações </string>
167 <string name="close">Fechar</string>
168 <string name="learn_more">Saber Mais</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Seleciona a driver para o GPU</string>
172 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
173 <string name="select_gpu_driver_install">Instalar</string>
174 <string name="select_gpu_driver_default">Padrão</string>
175 <string name="select_gpu_driver_install_success">Instalado%s</string>
176 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
177 <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
178 <string name="system_gpu_driver">Driver do GPU padrão</string>
179 <string name="installing_driver">A instalar o Driver...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Configurações</string>
183 <string name="preferences_general">Geral</string>
184 <string name="preferences_system">Sistema</string>
185 <string name="preferences_graphics">Gráficos</string>
186 <string name="preferences_audio">Audio</string>
187 <string name="preferences_theme">Cor e tema.</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">A tua ROM está encriptada</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor segue os guias para fazer redump das tuas<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">Cartidges de Jogo</a> or <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">Jogos Instalados</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Por favor confirma que o teu ficheiro <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> está instalado para que os jogos possam ser desencriptados.]]></string>
193 <string name="loader_error_video_core">Ocorreu um erro ao iniciar o núcleo de vídeo.</string>
194 <string name="loader_error_video_core_description">Isto é normalmente causado por um driver de GPU incompatível. Instalar um driver GPU pode resolver este problema.</string>
195 <string name="loader_error_invalid_format">Impossível carregar a tua ROM</string>
196 <string name="loader_error_file_not_found">O ficheiro da ROM não existe</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Sair da emulação</string>
200 <string name="emulation_done">Feito</string>
201 <string name="emulation_fps_counter">Contador de FPS</string>
202 <string name="emulation_toggle_controls">Alterar Controlos</string>
203 <string name="emulation_rel_stick_center">Centro do Analógico Relativo</string>
204 <string name="emulation_dpad_slide">Deslizar do DPad</string>
205 <string name="emulation_haptics">Hápticos </string>
206 <string name="emulation_show_overlay">Mostrar sobreposição </string>
207 <string name="emulation_toggle_all">Alterar todos</string>
208 <string name="emulation_control_adjust">Ajustar a sobreposição </string>
209 <string name="emulation_control_scale">Escala</string>
210 <string name="emulation_control_opacity">Opacidade</string>
211 <string name="emulation_touch_overlay_reset">Redefinir Sobreposição </string>
212 <string name="emulation_touch_overlay_edit">Editar sobreposição </string>
213 <string name="emulation_pause">Pausa emulação</string>
214 <string name="emulation_unpause">Retomar emulação</string>
215 <string name="emulation_input_overlay">Opções de sobreposição </string>
216 <string name="emulation_game_loading">Jogo a carregar...</string>
217
218 <string name="load_settings">Configurações a carregar...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Teclado de Software</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Abortar</string>
225 <string name="continue_button">Continuar</string>
226 <string name="system_archive_not_found">Arquivo do Sistema Não Encontrado</string>
227 <string name="system_archive_not_found_message">%s está em falta. Por favor apaga os teus ficheiros de sistema.\nContinuar a emulação pode causar erros.</string>
228 <string name="system_archive_general">Um arquivo do sistema</string>
229 <string name="save_load_error">Erro Guardar/Carregar</string>
230 <string name="fatal_error">Erro fatal</string>
231 <string name="fatal_error_message">Ocorreu um erro fatal. Verifica o teu registro para detalhes. \nContinuar a emulação pode causar erros.</string>
232 <string name="performance_warning">Desligar esta configuração irá reduzir a performance da emulação significantemente! Para a melhor experiência é recomendado que deixes esta configuração ativada.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Japão</string>
236 <string name="region_usa">EUA</string>
237 <string name="region_europe">Europa</string>
238 <string name="region_australia">Austrália</string>
239 <string name="region_china">China</string>
240 <string name="region_korea">Coreia</string>
241 <string name="region_taiwan">Taiwan</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Japonês (日本語)</string>
245 <string name="language_english">Inglês</string>
246 <string name="language_french">Francês (Français)</string>
247 <string name="langauge_german">Alemão (Deutsch)</string>
248 <string name="language_italian">Italiano (Italiano)</string>
249 <string name="language_spanish">Espanhol (Español)</string>
250 <string name="language_chinese">Chinês simplificado (简体中文)</string>
251 <string name="language_korean">Coreano (한국어)</string>
252 <string name="language_dutch">Holandês (Nederlands)</string>
253 <string name="language_portuguese">Português (Português)</string>
254 <string name="language_russian">Russo (Русский)</string>
255 <string name="language_taiwanese">Taiwanês (台湾)</string>
256 <string name="language_british_english">Inglês Britânico</string>
257 <string name="language_canadian_french">Fracês Canadiano (Français canadien)</string>
258 <string name="language_latin_american_spanish">Espanhol da América Latina (Español latino-americano)</string>
259 <string name="language_simplified_chinese">Chinês Simplificado (简体中文)</string>
260 <string name="language_traditional_chinese">Chinês Tradicional (正 體 中文)</string>
261 <string name="language_brazilian_portuguese">Português do Brasil (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulcano</string>
265 <string name="renderer_none">Nenhum</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Normal</string>
269 <string name="renderer_accuracy_high">Alto</string>
270 <string name="renderer_accuracy_extreme">Estremo (Lento)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Lento)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Lento)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Lento)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Imediato (Desligado)</string>
282 <string name="renderer_vsync_mailbox">Caixa de entrada</string>
283 <string name="renderer_vsync_fifo">FIFO (Ligado)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO Relaxado </string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Vizinho mais próximo</string>
288 <string name="scaling_filter_bilinear">Bilinear</string>
289 <string name="scaling_filter_bicubic">Bicúbico</string>
290 <string name="scaling_filter_gaussian">Gaussiano</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Nenhum</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">Padrão (16:9)</string>
301 <string name="ratio_force_four_three">Forçar 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Forçar 21:9</string>
303 <string name="ratio_force_sixteen_ten">Forçar 16:10</string>
304 <string name="ratio_stretch">Esticar à Janela</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Preciso</string>
308 <string name="cpu_accuracy_unsafe">Inseguro</string>
309 <string name="cpu_accuracy_paranoid">Paranoid (Lento)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">D-Pad</string>
313 <string name="gamepad_left_stick">Analógico Esquerdo</string>
314 <string name="gamepad_right_stick">Analógico Direito</string>
315 <string name="gamepad_home">Home</string>
316 <string name="gamepad_screenshot">Captura de ecrã</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">A preparar shaders</string>
320 <string name="building_shaders">A criar shaders</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Muda o Tema da App</string>
324 <string name="theme_default">Padrão</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Altera o Modo do Tema</string>
329 <string name="theme_mode_follow_system">Igual ao Sistema</string>
330 <string name="theme_mode_light">Claro</string>
331 <string name="theme_mode_dark">Escuro</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Usa Fundos Escuros</string>
335 <string name="use_black_backgrounds_description">Quando usar tema escuro, aplicar fundos escuros</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..0fb4908f7
--- /dev/null
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Это программное обеспечение позволяет запускать игры для игровой консоли Nintendo Switch. Мы не предоставляем сами игры или ключи.&lt;br /&gt;&lt;br /&gt;Перед началом работы найдите файл <![CDATA[<b> prod.keys </b>]]> в хранилище устройства..&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Узнать больше</a>]]></string>
5 <string name="emulation_notification_channel_name">Эмуляция активна</string>
6 <string name="emulation_notification_channel_description">Показывает постоянное уведомление, когда запущена эмуляция.</string>
7 <string name="emulation_notification_running">yuzu запущен</string>
8 <string name="notice_notification_channel_name">Уведомления и ошибки</string>
9 <string name="notice_notification_channel_description">Показывать уведомления, когда что-то пошло не так</string>
10 <string name="notification_permission_not_granted">Вы не предоставили разрешение уведомлений!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Добро пожаловать!</string>
14 <string name="welcome_description">Узнайте, как настроить &lt;b>yuzu&lt;/b> и перейти прямиком к эмуляции.</string>
15 <string name="get_started">Начать</string>
16 <string name="keys">Ключи</string>
17 <string name="keys_description">Выберите ваш файл &lt;b>prod.keys&lt;/b> с помощью кнопки ниже.</string>
18 <string name="select_keys">Выбрать ключи</string>
19 <string name="games">Игры</string>
20 <string name="games_description">Выберите вашу папку с &lt;b>играми&lt;/b> с помощью кнопки ниже.</string>
21 <string name="done">Готово</string>
22 <string name="done_description">Все готово.\nМожно играть!</string>
23 <string name="text_continue">Продолжить</string>
24 <string name="next">Далее</string>
25 <string name="back">Назад</string>
26 <string name="add_games">Добавить игры</string>
27 <string name="add_games_description">Выберите папку с играми</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Игры</string>
31 <string name="home_search">Поиск</string>
32 <string name="home_settings">Настройки</string>
33 <string name="empty_gamelist">Не найдены файлы или еще не выбрана папка с играми.</string>
34 <string name="search_and_filter_games">Поиск и фильтрация игр</string>
35 <string name="select_games_folder">Выберите папку с играми</string>
36 <string name="select_games_folder_description">Позволяет yuzu заполнить список игр</string>
37 <string name="add_games_warning">Пропустить выбор папки с играми?</string>
38 <string name="add_games_warning_description">Игры не будут отображаться в списке Игры, если папка не выбрана.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Найти игры</string>
41 <string name="games_dir_selected">Выбрана папка с играми</string>
42 <string name="install_prod_keys">Установить prod.keys</string>
43 <string name="install_prod_keys_description">Требуется для расшифровки розничных игр</string>
44 <string name="install_prod_keys_warning">Пропустить добавление ключей?</string>
45 <string name="install_prod_keys_warning_description">Для эмуляции розничных игр требуются действительные ключи. Если вы продолжите, будут работать только homebrew приложения.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Уведомления</string>
48 <string name="notifications_description">Предоставьте разрешение уведомлений с помощью кнопки ниже.</string>
49 <string name="give_permission">Предоставить разрешение</string>
50 <string name="notification_warning">Пропустить предоставление разрешения уведомлений?</string>
51 <string name="notification_warning_description">yuzu не сможет уведомлять вас о важной информации.</string>
52 <string name="permission_denied">Разрешение отказано</string>
53 <string name="permission_denied_description">Вы слишком часто отклоняли это разрешение, и теперь вам нужно будет вручную предоставить его в настройках системы.</string>
54 <string name="about">О нас</string>
55 <string name="about_description">Версия сборки, титры и другое</string>
56 <string name="warning_help">Помощь</string>
57 <string name="warning_skip">Пропустить</string>
58 <string name="warning_cancel">Отмена</string>
59 <string name="install_amiibo_keys">Установить ключи Amiibo</string>
60 <string name="install_amiibo_keys_description">Необходимо для использования Amiibo в играх</string>
61 <string name="invalid_keys_file">Выбран неверный файл ключей</string>
62 <string name="install_keys_success">Ключи успешно установлены</string>
63 <string name="reading_keys_failure">Ошибка при чтении ключей шифрования</string>
64 <string name="invalid_keys_error">Неверные ключи шифрования</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">Выбранный файл неверен или поврежден. Пожалуйста, пере-дампите ваши ключи.</string>
67 <string name="install_gpu_driver">Установить драйвер ГП</string>
68 <string name="install_gpu_driver_description">Установите альтернативные драйверы для потенциально лучшей производительности и/или точности</string>
69 <string name="advanced_settings">Расширенные настройки</string>
70 <string name="settings_description">Настройка параметров эмулятора</string>
71 <string name="search_recently_played">Недавно сыграно</string>
72 <string name="search_recently_added">Недавно добавлено</string>
73 <string name="search_retail">Розничные</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Открыть папку yuzu</string>
76 <string name="open_user_folder_description">Управление внутренними файлами yuzu</string>
77 <string name="theme_and_color_description">Изменение внешнего вида приложения</string>
78 <string name="no_file_manager">Не найден файловый менеджер</string>
79 <string name="notification_no_directory_link">Не удалось открыть папку yuzu</string>
80 <string name="notification_no_directory_link_description">Пожалуйста, найдите папку пользователя с помощью боковой панели файлового менеджера вручную.</string>
81 <string name="manage_save_data">Управление данными сохранений</string>
82 <string name="manage_save_data_description">Найдено данные сохранений. Пожалуйста, выберите вариант ниже.</string>
83 <string name="import_export_saves_description">Импорт или экспорт файлов сохранения</string>
84 <string name="import_export_saves_no_profile">Данные сохранений не найдены. Пожалуйста, запустите игру и повторите попытку.</string>
85 <string name="save_file_imported_success">Успешно импортировано</string>
86 <string name="save_file_invalid_zip_structure">Недопустимая структура папки сохранения</string>
87 <string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string>
88 <string name="import_saves">Импорт</string>
89 <string name="export_saves">Экспорт</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia не существует</string>
93 <string name="copied_to_clipboard">Скопировано в буфер обмена</string>
94 <string name="about_app_description">Эмулятор Switch с открытым исходным кодом</string>
95 <string name="contributors">Контрибьюторы</string>
96 <string name="contributors_description">Сделано с \u2764 от команды yuzu</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Сборка</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Ранний доступ</string>
105 <string name="get_early_access">Получить ранний доступ</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Новейшие возможности, ранний доступ к обновлениям и другое</string>
108 <string name="early_access_benefits">Преимущества раннего доступа</string>
109 <string name="cutting_edge_features">Новейшие возможности</string>
110 <string name="early_access_updates">Ранний доступ к обновлениям</string>
111 <string name="no_manual_installation">Без ручной установки</string>
112 <string name="prioritized_support">Приоритетная поддержка</string>
113 <string name="helping_game_preservation">Помощь в презервации игр</string>
114 <string name="our_eternal_gratitude">Наша бесконечная благодарность</string>
115 <string name="are_you_interested">Вы заинтересованы?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Включить ограничение скорости</string>
119 <string name="frame_limit_enable_description">Если эта функция включена, скорость эмуляции будет ограничена указанным процентом от нормальной скорости.</string>
120 <string name="frame_limit_slider">Ограничение процента cкорости</string>
121 <string name="frame_limit_slider_description">Указывает процент для ограничения скорости эмуляции. При значении по умолчанию 100% эмуляция будет ограничена нормальной скоростью. Значения выше или ниже будут увеличивать или уменьшать ограничение скорости.</string>
122 <string name="cpu_accuracy">Точность ЦП</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Режим док-станции</string>
126 <string name="use_docked_mode_description">Эмуляция режима док-станции, что увеличивает разрешение за счет снижения производительности.</string>
127 <string name="emulated_region">Эмулируемый регион</string>
128 <string name="emulated_language">Эмулируемый язык</string>
129 <string name="select_rtc_date">Выберите дату RTC</string>
130 <string name="select_rtc_time">Выберите время RTC</string>
131 <string name="use_custom_rtc">Включить пользовательский RTC</string>
132 <string name="use_custom_rtc_description">Этот параметр позволяет установить пользовательские часы реального времени отдельно от текущего системного времени</string>
133 <string name="set_custom_rtc">Установить пользовательский RTC</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">Уровень точности</string>
138 <string name="renderer_resolution">Разрешение</string>
139 <string name="renderer_vsync">Режим верт. синхронизации</string>
140 <string name="renderer_aspect_ratio">Соотношение сторон</string>
141 <string name="renderer_scaling_filter">Фильтр адаптации окна</string>
142 <string name="renderer_anti_aliasing">Метод сглаживания</string>
143 <string name="renderer_force_max_clock">Принудительно заставить максимальную тактовую частоту (только для Adreno)</string>
144 <string name="renderer_force_max_clock_description">Заставляет ГП работать на максимально возможных тактовых частотах (тепловые ограничения все равно будут применяться).</string>
145 <string name="renderer_asynchronous_shaders">Использовать асинхронные шейдеры</string>
146 <string name="renderer_asynchronous_shaders_description">Компилирует шейдеры асинхронно, что уменьшает зависания, но может взамен предоставить визуальные баги.</string>
147 <string name="renderer_debug">Включить отладку графики</string>
148 <string name="renderer_debug_description">Если включено, графический API переходит в более медленный режим отладки</string>
149 <string name="use_disk_shader_cache">Использовать кэш шейдеров на диске</string>
150 <string name="use_disk_shader_cache_description">Уменьшение зависаний за счет хранения и загрузки сгенерированных шейдеров на хранилище.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Громкость</string>
154 <string name="audio_volume_description">Задает громкость аудиовыхода.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">По умолчанию</string>
158 <string name="ini_saved">Сохраненные настройки</string>
159 <string name="gameid_saved">Настройки сохранены для %1$s</string>
160 <string name="error_saving">Ошибка сохранения %1$s.ini: %2$s</string>
161 <string name="loading">Загрузка...</string>
162 <string name="reset_setting_confirmation">Хотите ли вы вернуть этот параметр к значению по умолчанию?</string>
163 <string name="reset_to_default">Сброс к настройкам по умолчанию</string>
164 <string name="reset_all_settings">Сбросить все настройки?</string>
165 <string name="reset_all_settings_description">Все дополнительные настройки будут сброшены к настройке по умолчанию. Это невозможно отменить.</string>
166 <string name="settings_reset">Настройки сброшены</string>
167 <string name="close">Закрыть</string>
168 <string name="learn_more">Узнать больше</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Выбрать драйвер ГП</string>
172 <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
173 <string name="select_gpu_driver_install">Установить</string>
174 <string name="select_gpu_driver_default">По умолчанию</string>
175 <string name="select_gpu_driver_install_success">Установлено %s</string>
176 <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
177 <string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
178 <string name="system_gpu_driver">Системный драйвер ГП</string>
179 <string name="installing_driver">Установка драйвера...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Настройки</string>
183 <string name="preferences_general">Общие</string>
184 <string name="preferences_system">Система</string>
185 <string name="preferences_graphics">Графика</string>
186 <string name="preferences_audio">Аудио</string>
187 <string name="preferences_theme">Тема и цвет</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">Ваш ROM зашифрованный</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Пожалуйста, следуйте инструкциям, чтобы пере-дампить ваши <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">игровые картриджи</a> или <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">установленные игры</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Пожалуйста, убедитесь, что ваш файл <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> установлен, чтобы игры можно было расшифровать.]]></string>
193 <string name="loader_error_video_core">Произошла ошибка при инициализации видеоядра.</string>
194 <string name="loader_error_video_core_description">Обычно это вызвано несовместимым драйвером ГП. Установка пользовательского драйвера ГП может решить эту проблему.</string>
195 <string name="loader_error_invalid_format">Не удалось запустить ROM</string>
196 <string name="loader_error_file_not_found">Файл ROM не существует</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Выход из эмуляции</string>
200 <string name="emulation_done">Готово</string>
201 <string name="emulation_fps_counter">Счётчик FPS</string>
202 <string name="emulation_toggle_controls">Переключение управления</string>
203 <string name="emulation_rel_stick_center">Относительный центр стика</string>
204 <string name="emulation_dpad_slide">Слайд крестовиной</string>
205 <string name="emulation_haptics">Тактильная обратная связь</string>
206 <string name="emulation_show_overlay">Показать оверлей</string>
207 <string name="emulation_toggle_all">Переключить всё</string>
208 <string name="emulation_control_adjust">Настроить оверлей</string>
209 <string name="emulation_control_scale">Масштаб</string>
210 <string name="emulation_control_opacity">Непрозрачность</string>
211 <string name="emulation_touch_overlay_reset">Сбросить оверлей</string>
212 <string name="emulation_touch_overlay_edit">Изменить оверлей</string>
213 <string name="emulation_pause">Пауза эмуляции</string>
214 <string name="emulation_unpause">Возобновление эмуляции</string>
215 <string name="emulation_input_overlay">Настройки оверлея</string>
216 <string name="emulation_game_loading">Загрузка игры...</string>
217
218 <string name="load_settings">Загрузка настроек...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Виртуальная клавиатура</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Прервать</string>
225 <string name="continue_button">Продолжить</string>
226 <string name="system_archive_not_found">Системный архив не найден</string>
227 <string name="system_archive_not_found_message">%s отсутствует. Пожалуйста, сдампите ваши системные архивы.\nПродолжение эмуляции может привести к сбоям и ошибкам.</string>
228 <string name="system_archive_general">Системный архив</string>
229 <string name="save_load_error">Ошибка сохранения/загрузки</string>
230 <string name="fatal_error">Фатальная ошибка</string>
231 <string name="fatal_error_message">Произошла фатальная ошибка. Проверьте журнал для получения подробной информации.\nПродолжение эмуляции может привести к сбоям и ошибкам.</string>
232 <string name="performance_warning">Отключение этой настройки значительно снизит производительность эмуляции! Для достижения наилучших результатов рекомендуется оставить эту настройку включенной.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Япония</string>
236 <string name="region_usa">США</string>
237 <string name="region_europe">Европа</string>
238 <string name="region_australia">Австралия</string>
239 <string name="region_china">Китай</string>
240 <string name="region_korea">Корея</string>
241 <string name="region_taiwan">Тайвань</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Японский (日本語)</string>
245 <string name="language_english">Английский (English)</string>
246 <string name="language_french">Французский (Français)</string>
247 <string name="langauge_german">Немецкий (Deutsch)</string>
248 <string name="language_italian">Итальянский (Italiano)</string>
249 <string name="language_spanish">Испанский (Español)</string>
250 <string name="language_chinese">Китайский (简体中文)</string>
251 <string name="language_korean">Корейский (한국어)</string>
252 <string name="language_dutch">Голландский (Nederlands)</string>
253 <string name="language_portuguese">Португальский (Português)</string>
254 <string name="language_russian">Русский</string>
255 <string name="language_taiwanese">Тайваньский (台湾)</string>
256 <string name="language_british_english">Британский английский</string>
257 <string name="language_canadian_french">Канадский французский (Français canadien)</string>
258 <string name="language_latin_american_spanish">Латиноамериканский испанский (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">Упрощенный китайский (简体中文)</string>
260 <string name="language_traditional_chinese">Традиционный китайский (正體中文)</string>
261 <string name="language_brazilian_portuguese">Бразильский португальский (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">Никакой</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Нормальная</string>
269 <string name="renderer_accuracy_high">Высокая</string>
270 <string name="renderer_accuracy_extreme">Экстрим (медленный)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Медленно)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Медленно)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Медленно)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Моментальная (выключена) </string>
282 <string name="renderer_vsync_mailbox">Mailbox</string>
283 <string name="renderer_vsync_fifo">FIFO (Включена)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Ближайший сосед</string>
288 <string name="scaling_filter_bilinear">Билинейный</string>
289 <string name="scaling_filter_bicubic">Бикубический</string>
290 <string name="scaling_filter_gaussian">Гаусс</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™️ Super Resolution</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Выкл.</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">Стандартное (16:9)</string>
301 <string name="ratio_force_four_three">Заставить 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Заставить 21:9</string>
303 <string name="ratio_force_sixteen_ten">Заставить 16:10</string>
304 <string name="ratio_stretch">Растянуть до окна</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Точно</string>
308 <string name="cpu_accuracy_unsafe">Небезопасно</string>
309 <string name="cpu_accuracy_paranoid">Параноик (медленно)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">Крестовина</string>
313 <string name="gamepad_left_stick">Левый мини-джойстик</string>
314 <string name="gamepad_right_stick">Правый мини-джойстик</string>
315 <string name="gamepad_home">Home</string>
316 <string name="gamepad_screenshot">Скриншот</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">Подготовка шейдеров</string>
320 <string name="building_shaders">Постройка шейдеров</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Изменить тему приложения</string>
324 <string name="theme_default">По умолчанию</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Изменить режим темы</string>
329 <string name="theme_mode_follow_system">Системная</string>
330 <string name="theme_mode_light">Светлая</string>
331 <string name="theme_mode_dark">Темная</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Использовать черный фон</string>
335 <string name="use_black_backgrounds_description">При использовании темной темы применяйте черный фон.</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
new file mode 100644
index 000000000..0d11eb2d2
--- /dev/null
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">Це програмне забезпечення дозволяє запускати ігри для ігрової консолі Nintendo Switch. Ми не надаємо самі ігри або ключі.&lt;br /&gt;&lt;br /&gt;Перед початком роботи знайдіть ваш файл <![CDATA[<b> prod.keys </b>]]> у сховищі пристрою.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Дізнатися більше</a>]]></string>
5 <string name="emulation_notification_channel_name">Емуляція активна</string>
6 <string name="emulation_notification_channel_description">Показує постійне сповіщення, коли запущено емуляцію.</string>
7 <string name="emulation_notification_running">yuzu запущено</string>
8 <string name="notice_notification_channel_name">Сповіщення та помилки</string>
9 <string name="notice_notification_channel_description">Показувати сповіщення, коли щось пішло не так</string>
10 <string name="notification_permission_not_granted">Ви не надали дозвіл сповіщень!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">Вітаємо!</string>
14 <string name="welcome_description">Дізнайтеся, як налаштувати &lt;b>yuzu&lt;/b> та перейти до емуляції.</string>
15 <string name="get_started">Розпочати</string>
16 <string name="keys">Ключі</string>
17 <string name="keys_description">Виберіть ваш файл &lt;b>prod.keys&lt;/b> за допомогою кнопки нижче.</string>
18 <string name="select_keys">Вибрати ключі</string>
19 <string name="games">Ігри</string>
20 <string name="games_description">Виберіть вашу папку з &lt;b>іграми&lt;/b> за допомогою кнопки нижче.</string>
21 <string name="done">Готово</string>
22 <string name="done_description">Все готово.\nМожна грати!</string>
23 <string name="text_continue">Продовжити</string>
24 <string name="next">Далі</string>
25 <string name="back">Назад</string>
26 <string name="add_games">Додати ігри</string>
27 <string name="add_games_description">Виберіть папку з іграми</string>
28
29 <!-- Home strings -->
30 <string name="home_games">Ігри</string>
31 <string name="home_search">Пошук</string>
32 <string name="home_settings">Налаштування</string>
33 <string name="empty_gamelist">Не знайдено файлів або ще не вибрано папку з іграми.</string>
34 <string name="search_and_filter_games">Пошук та фільтрація ігор</string>
35 <string name="select_games_folder">Виберіть папку з іграми</string>
36 <string name="select_games_folder_description">Дозволяє yuzu заповнити список ігор</string>
37 <string name="add_games_warning">Пропустити вибір папки з іграми?</string>
38 <string name="add_games_warning_description">Ігри не відображатимуться у списку Ігри, якщо папку не вибрано.</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">Знайти ігри</string>
41 <string name="games_dir_selected">Вибрано папку з іграми</string>
42 <string name="install_prod_keys">Встановити prod.keys</string>
43 <string name="install_prod_keys_description">Потрібно для розшифровки роздрібних ігор</string>
44 <string name="install_prod_keys_warning">Пропустити додавання ключів?</string>
45 <string name="install_prod_keys_warning_description">Для емуляції роздрібних ігор потрібні дійсні ключі. Якщо ви продовжите, працюватимуть тільки homebrew додатки.</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">Сповіщення</string>
48 <string name="notifications_description">Надайте дозвіл сповіщень за допомогою кнопки нижче.</string>
49 <string name="give_permission">Надати дозвіл</string>
50 <string name="notification_warning">Пропустити надання дозволу сповіщень?</string>
51 <string name="notification_warning_description">yuzu не зможе повідомляти вас про важливу інформацію.</string>
52 <string name="permission_denied">У дозволі відмовлено</string>
53 <string name="permission_denied_description">Ви занадто часто відхиляли цей дозвіл, тож тепер вам потрібно буде вручну надати його в системних налаштуваннях.</string>
54 <string name="about">Про нас</string>
55 <string name="about_description">Версія збірки, титри та інше</string>
56 <string name="warning_help">Допомога</string>
57 <string name="warning_skip">Пропустити</string>
58 <string name="warning_cancel">Відміна</string>
59 <string name="install_amiibo_keys">Встановити ключі Amiibo</string>
60 <string name="install_amiibo_keys_description">Необхідно для використання Amiibo в іграх</string>
61 <string name="invalid_keys_file">Вибрано неправильний файл ключів</string>
62 <string name="install_keys_success">Ключі успішно встановлено</string>
63 <string name="reading_keys_failure">Помилка під час зчитування ключів шифрування</string>
64 <string name="invalid_keys_error">Невірні ключі шифрування</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">Обраний файл невірний або пошкоджений. Будь ласка, пере-дампіть ваші ключі.</string>
67 <string name="install_gpu_driver">Встановити драйвер ГП</string>
68 <string name="install_gpu_driver_description">Встановіть альтернативні драйвери для потенційно кращої продуктивності та/або точності</string>
69 <string name="advanced_settings">Розширені налаштування</string>
70 <string name="settings_description">Налаштування параметрів емулятора</string>
71 <string name="search_recently_played">Нещодавно зіграно</string>
72 <string name="search_recently_added">Нещодавно додано</string>
73 <string name="search_retail">Роздрібні</string>
74 <string name="search_homebrew">Homebrew</string>
75 <string name="open_user_folder">Відкрити папку yuzu</string>
76 <string name="open_user_folder_description">Керування внутрішніми файлами yuzu</string>
77 <string name="theme_and_color_description">Змінити зовнішній вигляд застосунку</string>
78 <string name="no_file_manager">Не знайдено файлового менеджера</string>
79 <string name="notification_no_directory_link">Не вдалося відкрити папку yuzu</string>
80 <string name="notification_no_directory_link_description">Будь ласка, знайдіть папку користувача за допомогою бічної панелі файлового менеджера вручну.</string>
81 <string name="manage_save_data">Керування даними збережень</string>
82 <string name="manage_save_data_description">Знайдено дані збережень. Будь ласка, виберіть варіант нижче.</string>
83 <string name="import_export_saves_description">Імпорт або експорт файлів збереження</string>
84 <string name="import_export_saves_no_profile">Дані збережень не знайдено. Будь ласка, запустіть гру та повторіть спробу.</string>
85 <string name="save_file_imported_success">Успішно імпортовано</string>
86 <string name="save_file_invalid_zip_structure">Неприпустима структура папки збереження</string>
87 <string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string>
88 <string name="import_saves">Імпорт</string>
89 <string name="export_saves">Експорт</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia не існує</string>
93 <string name="copied_to_clipboard">Скопійовано в буфер обміну</string>
94 <string name="about_app_description">Емулятор Switch із відкритим першокодом</string>
95 <string name="contributors">Вкладники</string>
96 <string name="contributors_description">Зроблено з \u2764 від команди yuzu</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">Збірка</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">Ранній доступ</string>
105 <string name="get_early_access">Отримати ранній доступ</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">Новітні можливості, ранній доступ до оновлень та інше</string>
108 <string name="early_access_benefits">Переваги раннього доступу</string>
109 <string name="cutting_edge_features">Новітні можливості</string>
110 <string name="early_access_updates">Ранній доступ до оновлень</string>
111 <string name="no_manual_installation">Без ручного встановлення</string>
112 <string name="prioritized_support">Пріоритетна підтримка</string>
113 <string name="helping_game_preservation">Допомога в презервації ігор</string>
114 <string name="our_eternal_gratitude">Наша нескінченна вдячність</string>
115 <string name="are_you_interested">Ви зацікавлені?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">Увімкнути обмеження швидкості</string>
119 <string name="frame_limit_enable_description">Якщо цю функцію ввімкнено, швидкість емуляції буде обмежена зазначеним відсотком від нормальної швидкості.</string>
120 <string name="frame_limit_slider">Обмеження відсотка швидкості</string>
121 <string name="frame_limit_slider_description">Вказує відсоток для обмеження швидкості емуляції. При значенні за замовчуванням 100% емуляція буде обмежена нормальною швидкістю. Значення вище або нижче збільшуватимуть або зменшуватимуть обмеження швидкості.</string>
122 <string name="cpu_accuracy">Точність ЦП</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">Режим док-станції</string>
126 <string name="use_docked_mode_description">Емуляція режиму док-станції, що збільшує роздільну здатність за рахунок зниження продуктивності.</string>
127 <string name="emulated_region">Емульований регіон</string>
128 <string name="emulated_language">Емульована мова</string>
129 <string name="select_rtc_date">Оберіть дату RTC</string>
130 <string name="select_rtc_time">Оберіть час RTC</string>
131 <string name="use_custom_rtc">Увімкнути користувацький RTC</string>
132 <string name="use_custom_rtc_description">Цей параметр дає змогу встановити користувацький годинник реального часу окремо від поточного системного часу</string>
133 <string name="set_custom_rtc">Встановити користувацький RTC</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">Рівень точності</string>
138 <string name="renderer_resolution">Роздільна здатність</string>
139 <string name="renderer_vsync">Режим верт. синхронізації</string>
140 <string name="renderer_aspect_ratio">Співвідношення сторін</string>
141 <string name="renderer_scaling_filter">Фільтр адаптації вікна</string>
142 <string name="renderer_anti_aliasing">Метод згладжування</string>
143 <string name="renderer_force_max_clock">Примусово змусити максимальну тактову частоту (тільки для Adreno)</string>
144 <string name="renderer_force_max_clock_description">Змушує ГП працювати на максимально можливих тактових частотах (теплові обмеження все одно будуть застосовуватися).</string>
145 <string name="renderer_asynchronous_shaders">Використовувати асинхронні шейдери</string>
146 <string name="renderer_asynchronous_shaders_description">Компілює шейдери асинхронно, що зменшує зависання, але може натомість надати візуальні баги.</string>
147 <string name="renderer_debug">Увімкнути налагодження графіки</string>
148 <string name="renderer_debug_description">Якщо увімкнено, графічний API переходить у повільніший режим налагодження</string>
149 <string name="use_disk_shader_cache">Використовувати кеш шейдерів на диску</string>
150 <string name="use_disk_shader_cache_description">Зменшення зависань завдяки зберіганню та завантаженню згенерованих шейдерів на сховище.</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">Гучність</string>
154 <string name="audio_volume_description">Вказує гучність аудіовиходу.</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">За замовчуванням</string>
158 <string name="ini_saved">Збережені налаштування</string>
159 <string name="gameid_saved">Налаштування збережені для %1$s</string>
160 <string name="error_saving">Помилка збереження %1$s.ini: %2$s</string>
161 <string name="loading">Завантаження...</string>
162 <string name="reset_setting_confirmation">Чи хочете ви повернути цей параметр до значення за замовчуванням?</string>
163 <string name="reset_to_default">Скидання до налаштувань за замовчуванням</string>
164 <string name="reset_all_settings">Скинути всі налаштування</string>
165 <string name="reset_all_settings_description">Усі додаткові налаштування буде скинуто до налаштування за замовчуванням. Це неможливо скасувати.</string>
166 <string name="settings_reset">Налаштування скинуто</string>
167 <string name="close">Закрити</string>
168 <string name="learn_more">Дізнатися більше</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">Вибрати драйвер ГП</string>
172 <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
173 <string name="select_gpu_driver_install">Встановити</string>
174 <string name="select_gpu_driver_default">За замовчуванням</string>
175 <string name="select_gpu_driver_install_success">Встановлено %s</string>
176 <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
177 <string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
178 <string name="system_gpu_driver">Системний драйвер ГП</string>
179 <string name="installing_driver">Встановлення драйвера...</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">Налаштування</string>
183 <string name="preferences_general">Загальні</string>
184 <string name="preferences_system">Система</string>
185 <string name="preferences_graphics">Графіка</string>
186 <string name="preferences_audio">Аудіо</string>
187 <string name="preferences_theme">Тема і колір</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">Ваш ROM зашифрований</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[Будь ласка, дотримуйтесь інструкцій, щоб пере-дампити ваші <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">ігрові картриджі</a> або <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">встановлені ігри</a>.]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[Будь ласка, переконайтеся, що ваш файл <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> встановлено, щоб ігри можна було розшифрувати.]]></string>
193 <string name="loader_error_video_core">Сталася помилка під час ініціалізації відеоядра.</string>
194 <string name="loader_error_video_core_description">Зазвичай це спричинено несумісним драйвером ГП. Встановлення користувацького драйвера ГП може вирішити цю проблему.</string>
195 <string name="loader_error_invalid_format">Не вдалося запустити ROM</string>
196 <string name="loader_error_file_not_found">Файл ROM не існує</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">Вихід з емуляції</string>
200 <string name="emulation_done">Готово</string>
201 <string name="emulation_fps_counter">Лічильник FPS</string>
202 <string name="emulation_toggle_controls">Перемикання керування</string>
203 <string name="emulation_rel_stick_center">Відносний центр стіка</string>
204 <string name="emulation_dpad_slide">Слайд хрестовиною</string>
205 <string name="emulation_haptics">Тактильний зворотний зв\'язок</string>
206 <string name="emulation_show_overlay">Показати оверлей</string>
207 <string name="emulation_toggle_all">Перемкнути все</string>
208 <string name="emulation_control_adjust">Налаштувати оверлей</string>
209 <string name="emulation_control_scale">Масштаб</string>
210 <string name="emulation_control_opacity">Непрозорість</string>
211 <string name="emulation_touch_overlay_reset">Скинути оверлей</string>
212 <string name="emulation_touch_overlay_edit">Змінити оверлей</string>
213 <string name="emulation_pause">Пауза емуляції</string>
214 <string name="emulation_unpause">Відновлення емуляції</string>
215 <string name="emulation_input_overlay">Налаштування оверлея</string>
216 <string name="emulation_game_loading">Завантаження гри...</string>
217
218 <string name="load_settings">Завантаження налаштувань...</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">Віртуальна клавіатура</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">Перервати</string>
225 <string name="continue_button">Продовжити</string>
226 <string name="system_archive_not_found">Системний архів не знайдено</string>
227 <string name="system_archive_not_found_message">%s відсутній. Будь ласка, здампіть ваші системні архіви.\nПродовження емуляції може призвести до збоїв і помилок.</string>
228 <string name="system_archive_general">Системний архів</string>
229 <string name="save_load_error">Помилка збереження/завантаження</string>
230 <string name="fatal_error">Фатальна помилка</string>
231 <string name="fatal_error_message">Сталася фатальна помилка. Перевірте журнал для отримання докладної інформації.\nПродовження емуляції може призвести до збоїв і помилок.</string>
232 <string name="performance_warning">Вимкнення цього налаштування значно знизить продуктивність емуляції! Для досягнення найкращих результатів рекомендується залишити це налаштування увімкненим.</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">Японія</string>
236 <string name="region_usa">США</string>
237 <string name="region_europe">Європа</string>
238 <string name="region_australia">Австралія</string>
239 <string name="region_china">Китай</string>
240 <string name="region_korea">Корея</string>
241 <string name="region_taiwan">Тайвань</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">Японська (日本語)</string>
245 <string name="language_english">Англійська (English)</string>
246 <string name="language_french">Французька (Français)</string>
247 <string name="langauge_german">Німецька (Deutsch)</string>
248 <string name="language_italian">Італійська (Italiano)</string>
249 <string name="language_spanish">Іспанська (Español)</string>
250 <string name="language_chinese">Китайскька (简体中文)</string>
251 <string name="language_korean">Корейська (한국어)</string>
252 <string name="language_dutch">Голландська (Nederlands)</string>
253 <string name="language_portuguese">Португальська (Português)</string>
254 <string name="language_russian">Російська (Русский)</string>
255 <string name="language_taiwanese">Тайванська (台湾)</string>
256 <string name="language_british_english">Британська англійська</string>
257 <string name="language_canadian_french">Канадська французька (Français canadien)</string>
258 <string name="language_latin_american_spanish">Латиноамериканська іспанська (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">Спрощена китайська (简体中文)</string>
260 <string name="language_traditional_chinese">Традиційна китайська (正體中文)</string>
261 <string name="language_brazilian_portuguese">Бразильська португальська (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">Вимкнено</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">Нормальна</string>
269 <string name="renderer_accuracy_high">Висока</string>
270 <string name="renderer_accuracy_extreme">Екстрим (повільно)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (Повільно)</string>
277 <string name="resolution_three">3X (2160p/3240p) (Повільно)</string>
278 <string name="resolution_four">4X (2880p/4320p) (Повільно)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">Моментальна (вимкнена)</string>
282 <string name="renderer_vsync_mailbox">Mailbox</string>
283 <string name="renderer_vsync_fifo">FIFO (ввімкнута)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">Найближчий сусід</string>
288 <string name="scaling_filter_bilinear">Білінійне</string>
289 <string name="scaling_filter_bicubic">Бікубічне</string>
290 <string name="scaling_filter_gaussian">Гауса</string>
291 <string name="scaling_filter_scale_force">ScaleForce</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">Вимкнено</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">За замовчуванням (16:9)</string>
301 <string name="ratio_force_four_three">Змусити 4:3</string>
302 <string name="ratio_force_twenty_one_nine">Змусити 21:9</string>
303 <string name="ratio_force_sixteen_ten">Змусити 16:10</string>
304 <string name="ratio_stretch">Розтягнути до вікна</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">Точно</string>
308 <string name="cpu_accuracy_unsafe">Небезпечно</string>
309 <string name="cpu_accuracy_paranoid">Параноїк (повільно)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">Кнопки напрямків</string>
313 <string name="gamepad_left_stick">Лівий міні-джойстик</string>
314 <string name="gamepad_right_stick">Правий міні-джойстик</string>
315 <string name="gamepad_home">Home</string>
316 <string name="gamepad_screenshot">Знімок екрану</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">Підготовка шейдерів</string>
320 <string name="building_shaders">Побудова шейдерів</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">Змінити тему застосунку</string>
324 <string name="theme_default">За замовчуванням</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">Змінити режим теми</string>
329 <string name="theme_mode_follow_system">Системна</string>
330 <string name="theme_mode_light">Світла</string>
331 <string name="theme_mode_dark">Темна</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">Використовувати чорне тло</string>
335 <string name="use_black_backgrounds_description">У разі використання темної теми застосовуйте чорне тло.</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-v31/themes.xml b/src/android/app/src/main/res/values-v31/themes.xml
new file mode 100644
index 000000000..5d3a86bf6
--- /dev/null
+++ b/src/android/app/src/main/res/values-v31/themes.xml
@@ -0,0 +1,31 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <style name="Theme.Yuzu.Main.MaterialYou" parent="Theme.Yuzu.Main">
5 <item name="colorPrimary">@color/m3_sys_color_dynamic_light_primary</item>
6 <item name="colorOnPrimary">@color/m3_sys_color_dynamic_light_on_primary</item>
7 <item name="colorPrimaryContainer">@color/m3_sys_color_dynamic_light_primary_container</item>
8 <item name="colorOnPrimaryContainer">@color/m3_sys_color_dynamic_light_on_primary_container</item>
9 <item name="colorSecondary">@color/m3_sys_color_dynamic_light_secondary</item>
10 <item name="colorOnSecondary">@color/m3_sys_color_dynamic_light_on_secondary</item>
11 <item name="colorSecondaryContainer">@color/m3_sys_color_dynamic_light_secondary_container</item>
12 <item name="colorOnSecondaryContainer">@color/m3_sys_color_dynamic_light_on_secondary_container</item>
13 <item name="colorTertiary">@color/m3_sys_color_dynamic_light_tertiary</item>
14 <item name="colorOnTertiary">@color/m3_sys_color_dynamic_light_on_tertiary</item>
15 <item name="colorTertiaryContainer">@color/m3_sys_color_dynamic_light_tertiary_container</item>
16 <item name="colorOnTertiaryContainer">@color/m3_sys_color_dynamic_light_on_tertiary_container</item>
17 <item name="android:colorBackground">@color/m3_sys_color_dynamic_light_background</item>
18 <item name="colorOnBackground">@color/m3_sys_color_dynamic_light_on_background</item>
19 <item name="colorSurface">@color/m3_sys_color_dynamic_light_surface</item>
20 <item name="colorOnSurface">@color/m3_sys_color_dynamic_light_on_surface</item>
21 <item name="colorSurfaceVariant">@color/m3_sys_color_dynamic_light_surface_variant</item>
22 <item name="colorOnSurfaceVariant">@color/m3_sys_color_dynamic_light_on_surface_variant</item>
23 <item name="colorOutline">@color/m3_sys_color_dynamic_light_outline</item>
24 <item name="colorOnSurfaceInverse">@color/m3_sys_color_dynamic_light_on_surface_variant</item>
25 <item name="colorSurfaceInverse">@color/m3_sys_color_dynamic_light_surface_variant</item>
26 <item name="colorPrimaryInverse">@color/m3_sys_color_dynamic_light_inverse_primary</item>
27
28 <item name="materialAlertDialogTheme">@style/ThemeOverlay.Material3.MaterialAlertDialog</item>
29 </style>
30
31</resources>
diff --git a/src/android/app/src/main/res/values-w600dp/bools.xml b/src/android/app/src/main/res/values-w600dp/bools.xml
new file mode 100644
index 000000000..b6833a702
--- /dev/null
+++ b/src/android/app/src/main/res/values-w600dp/bools.xml
@@ -0,0 +1,4 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3 <bool name="small_layout">false</bool>
4</resources>
diff --git a/src/android/app/src/main/res/values-w600dp/dimens.xml b/src/android/app/src/main/res/values-w600dp/dimens.xml
new file mode 100644
index 000000000..128319e27
--- /dev/null
+++ b/src/android/app/src/main/res/values-w600dp/dimens.xml
@@ -0,0 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3 <dimen name="spacing_navigation">0dp</dimen>
4 <dimen name="spacing_navigation_rail">80dp</dimen>
5</resources>
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..e00bbaa2e
--- /dev/null
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,337 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">此软件可以运行 Nintendo Switch 游戏,但不包含任何游戏和密钥文件。&lt;br /&gt;&lt;br /&gt;在开始前,请找到放置于设备存储中的 <![CDATA[<b> prod.keys </b>]]> 文件。&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">了解更多</a>]]></string>
5 <string name="emulation_notification_channel_name">正在进行模拟</string>
6 <string name="emulation_notification_channel_description">在模拟运行时显示持久通知。</string>
7 <string name="emulation_notification_running">yuzu 正在运行</string>
8 <string name="notice_notification_channel_name">通知及错误提醒</string>
9 <string name="notice_notification_channel_description">当发生错误时显示通知。</string>
10 <string name="notification_permission_not_granted">未授予通知权限!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">欢迎!</string>
14 <string name="welcome_description">了解如何设置 &lt;b>yuzu&lt;/b> 并进行模拟。</string>
15 <string name="get_started">开始</string>
16 <string name="keys">密钥文件</string>
17 <string name="keys_description">使用下方的按钮来选择你的 &lt;b>prod.keys&lt;/b> 文件。</string>
18 <string name="select_keys">选择密钥文件</string>
19 <string name="games">游戏</string>
20 <string name="games_description">使用下方的按钮选择你的 &lt;b>游戏&lt;/b> 文件夹。</string>
21 <string name="done">完成</string>
22 <string name="done_description">你完成了全部设置。\n玩的开心!</string>
23 <string name="text_continue">继续</string>
24 <string name="next">下一步</string>
25 <string name="back">上一步</string>
26 <string name="add_games">添加游戏</string>
27 <string name="add_games_description">选择你的游戏文件夹</string>
28
29 <!-- Home strings -->
30 <string name="home_games">游戏</string>
31 <string name="home_search">搜索</string>
32 <string name="home_settings">设置</string>
33 <string name="empty_gamelist">找不到游戏,或者尚未选择游戏文件夹。</string>
34 <string name="search_and_filter_games">搜索游戏</string>
35 <string name="select_games_folder">选择游戏文件夹</string>
36 <string name="select_games_folder_description">允许 yuzu 填充游戏列表</string>
37 <string name="add_games_warning">跳过选择游戏文件夹?</string>
38 <string name="add_games_warning_description">如果未选择游戏文件夹,游戏将不会显示在游戏列表中。</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">搜索游戏</string>
41 <string name="games_dir_selected">已选择游戏文件夹</string>
42 <string name="install_prod_keys">安装 prod.keys 文件</string>
43 <string name="install_prod_keys_description">需要密钥文件来解密游戏</string>
44 <string name="install_prod_keys_warning">跳过添加密钥文件?</string>
45 <string name="install_prod_keys_warning_description">对于商业游戏,需要有效的密钥文件才能运行。如果没有密钥文件,将只能运行自制软件。</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">通知</string>
48 <string name="notifications_description">使用下方的按钮授予通知权限。</string>
49 <string name="give_permission">授予权限</string>
50 <string name="notification_warning">跳过授予通知权限?</string>
51 <string name="notification_warning_description">yuzu 将无法通知您重要信息。</string>
52 <string name="permission_denied">授权遭拒</string>
53 <string name="permission_denied_description">您曾多次拒绝权限请求,现在您需要在系统设置中手动授予权限。</string>
54 <string name="about">关于</string>
55 <string name="about_description">开发版本、贡献者、以及更多</string>
56 <string name="warning_help">帮助</string>
57 <string name="warning_skip">跳过</string>
58 <string name="warning_cancel">取消</string>
59 <string name="install_amiibo_keys">安装 Amiibo 密钥文件</string>
60 <string name="install_amiibo_keys_description">在遊戏中使用 Amiibo 时必需</string>
61 <string name="invalid_keys_file">选择的密钥文件无效</string>
62 <string name="install_keys_success">密钥文件已成功安装</string>
63 <string name="reading_keys_failure">读取加密密钥时出错</string>
64 <string name="invalid_keys_error">无效的加密密钥</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">选择的密钥文件不正确或已损坏。请重新转储密钥文件。</string>
67 <string name="install_gpu_driver">安装 GPU 驱动</string>
68 <string name="install_gpu_driver_description">安装替代的驱动程序以获得更好的性能和精度</string>
69 <string name="advanced_settings">高级选项</string>
70 <string name="settings_description">更改模拟器设置</string>
71 <string name="search_recently_played">最近游玩</string>
72 <string name="search_recently_added">最近添加</string>
73 <string name="search_retail">商业游戏</string>
74 <string name="search_homebrew">自制游戏</string>
75 <string name="open_user_folder">打开 yuzu 文件夹</string>
76 <string name="open_user_folder_description">管理 yuzu 内部文件</string>
77 <string name="theme_and_color_description">更改外观</string>
78 <string name="no_file_manager">找不到可用的文件管理器</string>
79 <string name="notification_no_directory_link">无法打开 yuzu 文件夹</string>
80 <string name="notification_no_directory_link_description">请使用文件管理器的侧部面板手动定位用户文件夹。</string>
81 <string name="manage_save_data">管理存档数据</string>
82 <string name="manage_save_data_description">已找到存档数据,请选择下方的选项。</string>
83 <string name="import_export_saves_description">导入或导出存档</string>
84 <string name="import_export_saves_no_profile">找不到存档数据,请启动游戏并重试。</string>
85 <string name="save_file_imported_success">已成功导入存档</string>
86 <string name="save_file_invalid_zip_structure">无效的存档目录</string>
87 <string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string>
88 <string name="import_saves">导入</string>
89 <string name="export_saves">导出</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia 不真实</string>
93 <string name="copied_to_clipboard">已复制到剪贴板</string>
94 <string name="about_app_description">一款开放源代码的 Switch 模拟器</string>
95 <string name="contributors">贡献者</string>
96 <string name="contributors_description">使用来自 yuzu 团队的 \u2764 制作</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">构建版本</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">抢先体验</string>
105 <string name="get_early_access">取得抢先体验</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">最新的功能、抢先更新、以及更多</string>
108 <string name="early_access_benefits">抢先体验的权益</string>
109 <string name="cutting_edge_features">最新功能</string>
110 <string name="early_access_updates">抢先更新</string>
111 <string name="no_manual_installation">无需手动安装</string>
112 <string name="prioritized_support">优先支持</string>
113 <string name="helping_game_preservation">帮助保留游戏</string>
114 <string name="our_eternal_gratitude">我们真诚的感激</string>
115 <string name="are_you_interested">您对此感兴趣吗?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">启用运行速度限制</string>
119 <string name="frame_limit_enable_description">启用后,模拟速度将限制在正常运行速度的指定百分比。</string>
120 <string name="frame_limit_slider">限制速度百分比</string>
121 <string name="frame_limit_slider_description">指定限制模拟速度的百分比。预设为 100%,此时模拟速度将被限制为标准速度。更高或更低的值将增加或降低速度限制上限。</string>
122 <string name="cpu_accuracy">CPU 精度</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">主机模式</string>
126 <string name="use_docked_mode_description">以主机模式进行模拟,牺牲性能并提高画面分辨率。</string>
127 <string name="emulated_region">模拟区域</string>
128 <string name="emulated_language">模拟语言</string>
129 <string name="select_rtc_date">选择日期</string>
130 <string name="select_rtc_time">选择时间</string>
131 <string name="use_custom_rtc">启用自定义系统时钟</string>
132 <string name="use_custom_rtc_description">此选项允许您设置与目前系统时间相独立的自定义系统时钟</string>
133 <string name="set_custom_rtc">设置自定义系统时钟</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">精度等级</string>
138 <string name="renderer_resolution">分辨率</string>
139 <string name="renderer_vsync">垂直同步模式</string>
140 <string name="renderer_aspect_ratio">屏幕纵横比</string>
141 <string name="renderer_scaling_filter">窗口滤镜</string>
142 <string name="renderer_anti_aliasing">抗锯齿方式</string>
143 <string name="renderer_force_max_clock">强制最大时钟 (仅限 Adreno)</string>
144 <string name="renderer_force_max_clock_description">强制 GPU 以最大时钟运行 (仍被温控限制)。</string>
145 <string name="renderer_asynchronous_shaders">使用异步着色器</string>
146 <string name="renderer_asynchronous_shaders_description">异步编译着色器,减少卡顿,但可能引入故障。</string>
147 <string name="renderer_debug">启用图形调试</string>
148 <string name="renderer_debug_description">启用时,图形 API 将进入较慢的调试模式。</string>
149 <string name="use_disk_shader_cache">使用磁盘着色器缓存</string>
150 <string name="use_disk_shader_cache_description">将生成的着色器缓存于磁盘中并进行读取以减少卡顿。</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">音量</string>
154 <string name="audio_volume_description">指定输出的音量。</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">系统默认</string>
158 <string name="ini_saved">已保存设置</string>
159 <string name="gameid_saved">已保存 %1$s 的设置</string>
160 <string name="error_saving">保存 %1$s.ini 时出错: %2$s</string>
161 <string name="loading">加载中…</string>
162 <string name="reset_setting_confirmation">您要将此设定重设为默认值吗?</string>
163 <string name="reset_to_default">恢复默认</string>
164 <string name="reset_all_settings">重置所有设置项?</string>
165 <string name="reset_all_settings_description">所有高级选项都将被重设,此动作无法还原。</string>
166 <string name="settings_reset">重设设置项</string>
167 <string name="close">关闭</string>
168 <string name="learn_more">了解更多</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">选择 GPU 驱动程序</string>
172 <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
173 <string name="select_gpu_driver_install">安装</string>
174 <string name="select_gpu_driver_default">系统默认</string>
175 <string name="select_gpu_driver_install_success">已安装 %s</string>
176 <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
177 <string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
178 <string name="system_gpu_driver">系统 GPU 驱动程序</string>
179 <string name="installing_driver">正在安装驱动程序…</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">设置</string>
183 <string name="preferences_general">通用</string>
184 <string name="preferences_system">系统</string>
185 <string name="preferences_graphics">图形</string>
186 <string name="preferences_audio">声音</string>
187 <string name="preferences_theme">主题和色彩</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">您的 ROM 已加密</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[请参考指南重新转储你的<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">游戏卡带</a>或<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">已安装的游戏</a>。]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[请确保 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> 文件已安装,使得游戏可以被解密。]]></string>
193 <string name="loader_error_video_core">初始化视频核心时发生错误</string>
194 <string name="loader_error_video_core_description">这通常由不兼容的 GPU 驱动程序造成,安装自定义 GPU 驱动程序可能解决此问题。</string>
195 <string name="loader_error_invalid_format">无法载入 ROM</string>
196 <string name="loader_error_file_not_found">ROM 文件不存在</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">退出模拟</string>
200 <string name="emulation_done">完成</string>
201 <string name="emulation_fps_counter">FPS 计数器</string>
202 <string name="emulation_toggle_controls">按键切换</string>
203 <string name="emulation_rel_stick_center">相对摇杆中心</string>
204 <string name="emulation_dpad_slide">十字方向键滑动</string>
205 <string name="emulation_haptics">触觉反馈</string>
206 <string name="emulation_show_overlay">显示虚拟按键</string>
207 <string name="emulation_toggle_all">全部切换</string>
208 <string name="emulation_control_adjust">调整虚拟按键</string>
209 <string name="emulation_control_scale">缩放</string>
210 <string name="emulation_control_opacity">不透明度</string>
211 <string name="emulation_touch_overlay_reset">重设虚拟按键</string>
212 <string name="emulation_touch_overlay_edit">编辑虚拟按键</string>
213 <string name="emulation_pause">暂停模拟</string>
214 <string name="emulation_unpause">继续模拟</string>
215 <string name="emulation_input_overlay">虚拟按键选项</string>
216 <string name="emulation_game_loading">载入游戏中…</string>
217
218 <string name="load_settings">正在载入设定…</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">软件键盘</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">中止</string>
225 <string name="continue_button">继续</string>
226 <string name="system_archive_not_found">未找到系统档案</string>
227 <string name="system_archive_not_found_message">%s 丢失,请转储您的系统档案。\n继续模拟可能造成崩溃和错误。</string>
228 <string name="system_archive_general">系统档案</string>
229 <string name="save_load_error">保存/载入发生错误</string>
230 <string name="fatal_error">致命错误</string>
231 <string name="fatal_error_message">发生致命错误,请查阅日志获取详细信息。\n继续模拟可能会造成崩溃和错误。</string>
232 <string name="performance_warning">关闭此项会显著降低模拟性能!建议您将此项保持为启用状态。</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">日本</string>
236 <string name="region_usa">美国</string>
237 <string name="region_europe">欧洲</string>
238 <string name="region_australia">澳大利亚</string>
239 <string name="region_china">中国</string>
240 <string name="region_korea">韩国</string>
241 <string name="region_taiwan">中国台湾</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">日语 (日本語)</string>
245 <string name="language_english">英语 (English)</string>
246 <string name="language_french">法语 (Français)</string>
247 <string name="langauge_german">德语 (Deutsch)</string>
248 <string name="language_italian">意大利语 (Italiano)</string>
249 <string name="language_spanish">西班牙语 (Español)</string>
250 <string name="language_chinese">中文 (简体中文)</string>
251 <string name="language_korean">韩语 (한국어)</string>
252 <string name="language_dutch">荷兰语 (Nederlands)</string>
253 <string name="language_portuguese">葡萄牙语 (Português)</string>
254 <string name="language_russian">俄语 (Русский)</string>
255 <string name="language_taiwanese">台湾中文 (台灣)</string>
256 <string name="language_british_english">英式英语</string>
257 <string name="language_canadian_french">加拿大法语 (Français canadien)</string>
258 <string name="language_latin_american_spanish">拉丁美洲西班牙语 (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">简体中文 (简体中文)</string>
260 <string name="language_traditional_chinese">繁体中文 (正體中文)</string>
261 <string name="language_brazilian_portuguese">巴西葡萄牙语 (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">无</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">正常</string>
269 <string name="renderer_accuracy_high">高</string>
270 <string name="renderer_accuracy_extreme">极高 (慢速)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (慢速)</string>
277 <string name="resolution_three">3X (2160p/3240p) (慢速)</string>
278 <string name="resolution_four">4X (2880p/4320p) (慢速)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">即时 (关闭)</string>
282 <string name="renderer_vsync_mailbox">Mailbox</string>
283 <string name="renderer_vsync_fifo">FIFO (开启)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">近邻取样</string>
288 <string name="scaling_filter_bilinear">双线性过滤</string>
289 <string name="scaling_filter_bicubic">双三线过滤</string>
290 <string name="scaling_filter_gaussian">高斯模糊</string>
291 <string name="scaling_filter_scale_force">强制缩放</string>
292 <string name="scaling_filter_fsr">AMD FidelityFX™️ 超级分辨率锐画技术</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">无</string>
296 <string name="anti_aliasing_fxaa">快速近似抗锯齿</string>
297 <string name="anti_aliasing_smaa">子像素形态学抗锯齿</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">默认 (16:9)</string>
301 <string name="ratio_force_four_three">强制 4:3</string>
302 <string name="ratio_force_twenty_one_nine">强制 21:9</string>
303 <string name="ratio_force_sixteen_ten">强制 16:10</string>
304 <string name="ratio_stretch">拉伸窗口</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_accurate">高精度</string>
308 <string name="cpu_accuracy_unsafe">低精度</string>
309 <string name="cpu_accuracy_paranoid">偏执模式 (慢速)</string>
310
311 <!-- Gamepad Buttons -->
312 <string name="gamepad_d_pad">十字方向键</string>
313 <string name="gamepad_left_stick">左摇杆</string>
314 <string name="gamepad_right_stick">右摇杆</string>
315 <string name="gamepad_home">Home</string>
316 <string name="gamepad_screenshot">截图</string>
317
318 <!-- Disk shader cache -->
319 <string name="preparing_shaders">正在准备着色器</string>
320 <string name="building_shaders">正在编译着色器</string>
321
322 <!-- Theme options -->
323 <string name="change_app_theme">切换主题</string>
324 <string name="theme_default">系统默认</string>
325 <string name="theme_material_you">Material You</string>
326
327 <!-- Theme Modes -->
328 <string name="change_theme_mode">主题模式</string>
329 <string name="theme_mode_follow_system">跟随系统</string>
330 <string name="theme_mode_light">浅色</string>
331 <string name="theme_mode_dark">深色</string>
332
333 <!-- Black backgrounds theme -->
334 <string name="use_black_backgrounds">使用黑色背景</string>
335 <string name="use_black_backgrounds_description">使用深色主题时,套用黑色背景。</string>
336
337</resources>
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..a54d04248
--- /dev/null
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,336 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string name="app_disclaimer">此軟體可以執行 Nintendo Switch 主機遊戲,但不包含任何遊戲和金鑰。&lt;br /&gt;&lt;br /&gt;在您開始前,請找到放置於您的裝置儲存空間的 <![CDATA[<b> prod.keys </b>]]> 檔案。&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">深入瞭解</a>]]></string>
5 <string name="emulation_notification_channel_name">模擬進行中</string>
6 <string name="emulation_notification_channel_description">在模擬執行時顯示持續通知。</string>
7 <string name="emulation_notification_running">yuzu 正在執行</string>
8 <string name="notice_notification_channel_name">通知和錯誤</string>
9 <string name="notice_notification_channel_description">發生錯誤時顯示通知。</string>
10 <string name="notification_permission_not_granted">未授予通知權限!</string>
11
12 <!-- Setup strings -->
13 <string name="welcome">歡迎!</string>
14 <string name="welcome_description">瞭解如何設定 &lt;b>yuzu&lt;/b> 並進入模擬。</string>
15 <string name="get_started">開始使用</string>
16 <string name="keys">金鑰</string>
17 <string name="keys_description">使用下方的按鈕選取您的 &lt;b>prod.keys&lt;/b> 檔案。</string>
18 <string name="select_keys">選取金鑰</string>
19 <string name="games">遊戲</string>
20 <string name="games_description">使用下方的按鈕選取您的&lt;b>遊戲&lt;/b>資料夾。</string>
21 <string name="done">完成</string>
22 <string name="done_description">您已準備就緒。\n盡情遊玩您的遊戲!</string>
23 <string name="text_continue">繼續</string>
24 <string name="next">下一步</string>
25 <string name="back">上一步</string>
26 <string name="add_games">新增遊戲</string>
27 <string name="add_games_description">選取您的遊戲資料夾</string>
28
29 <!-- Home strings -->
30 <string name="home_games">遊戲</string>
31 <string name="home_search">搜尋</string>
32 <string name="home_settings">設定</string>
33 <string name="empty_gamelist">找不到檔案,或者尚未選取遊戲目錄。</string>
34 <string name="search_and_filter_games">搜尋並篩選遊戲</string>
35 <string name="select_games_folder">選取遊戲資料夾</string>
36 <string name="select_games_folder_description">一律允許 yuzu 填入遊戲清單</string>
37 <string name="add_games_warning">跳過選取遊戲資料夾?</string>
38 <string name="add_games_warning_description">如果資料夾未選取,遊戲將不會顯示在遊戲清單。</string>
39 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
40 <string name="home_search_games">搜尋遊戲</string>
41 <string name="games_dir_selected">遊戲目錄已選取</string>
42 <string name="install_prod_keys">安裝 prod.keys</string>
43 <string name="install_prod_keys_description">需要解密零售遊戲</string>
44 <string name="install_prod_keys_warning">跳過新增金鑰?</string>
45 <string name="install_prod_keys_warning_description">模擬零售遊戲需要有效的金鑰,若要繼續,將僅有自製遊戲應用程式可以運作。</string>
46 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
47 <string name="notifications">通知</string>
48 <string name="notifications_description">使用下方的按鈕授予通知權限。</string>
49 <string name="give_permission">授予權限</string>
50 <string name="notification_warning">跳過授予通知權限?</string>
51 <string name="notification_warning_description">yuzu 將無法通知您重要資訊。</string>
52 <string name="permission_denied">權限遭拒</string>
53 <string name="permission_denied_description">您曾多次拒絕了權限要求,現在您需要在系統設定中手動授予權限。</string>
54 <string name="about">關於</string>
55 <string name="about_description">組建版本、製作群、以及更多</string>
56 <string name="warning_help">說明</string>
57 <string name="warning_skip">跳過</string>
58 <string name="warning_cancel">取消</string>
59 <string name="install_amiibo_keys">安裝 Amiibo 金鑰</string>
60 <string name="install_amiibo_keys_description">需要在遊戲中使用 Amiibo</string>
61 <string name="invalid_keys_file">無效的金鑰檔案已選取</string>
62 <string name="install_keys_success">金鑰已成功安裝</string>
63 <string name="reading_keys_failure">讀取加密金鑰時出現錯誤</string>
64 <string name="invalid_keys_error">無效的加密金鑰</string>
65 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
66 <string name="install_keys_failure_description">選取的檔案不正確或已損毀,請重新傾印您的金鑰。</string>
67 <string name="install_gpu_driver">安裝 GPU 驅動程式</string>
68 <string name="install_gpu_driver_description">安裝替代驅動程式以取得潛在的更佳效能或準確度</string>
69 <string name="advanced_settings">進階設定</string>
70 <string name="settings_description">進行模擬器設定</string>
71 <string name="search_recently_played">最近遊玩</string>
72 <string name="search_recently_added">最近新增</string>
73 <string name="search_retail">零售</string>
74 <string name="search_homebrew">自製遊戲</string>
75 <string name="open_user_folder">開啟 yuzu 資料夾</string>
76 <string name="open_user_folder_description">管理 yuzu 的內部檔案</string>
77 <string name="theme_and_color_description">修改應用程式外觀</string>
78 <string name="no_file_manager">找不到檔案管理員</string>
79 <string name="notification_no_directory_link">無法開啟 yuzu 目錄</string>
80 <string name="notification_no_directory_link_description">請使用檔案管理員的側邊面板手動定位到使用者資料夾。</string>
81 <string name="manage_save_data">管理儲存資料</string>
82 <string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string>
83 <string name="import_export_saves_description">匯入或匯出儲存檔案</string>
84 <string name="import_export_saves_no_profile">找不到儲存資料,請啟動遊戲並重試。</string>
85 <string name="save_file_imported_success">已成功匯入</string>
86 <string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string>
87 <string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string>
88 <string name="import_saves">匯入</string>
89 <string name="export_saves">匯出</string>
90
91 <!-- About screen strings -->
92 <string name="gaia_is_not_real">Gaia 不真實</string>
93 <string name="copied_to_clipboard">已複製到剪貼簿</string>
94 <string name="about_app_description">一個開放原始碼的 Switch 模擬器</string>
95 <string name="contributors">參與者</string>
96 <string name="contributors_description">使用來自 yuzu 團隊的 \u2764 製作</string>
97 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
98 <string name="build">組建</string>
99 <string name="support_link">https://discord.gg/u77vRWY</string>
100 <string name="website_link">https://yuzu-emu.org/</string>
101 <string name="github_link">https://github.com/yuzu-emu</string>
102
103 <!-- Early access upgrade strings -->
104 <string name="early_access">搶先體驗</string>
105 <string name="get_early_access">搶先體驗新功能</string>
106 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
107 <string name="get_early_access_description">最新的功能、搶先版更新、以及更多</string>
108 <string name="early_access_benefits">搶先體驗權益</string>
109 <string name="cutting_edge_features">最新功能</string>
110 <string name="early_access_updates">搶先版更新</string>
111 <string name="no_manual_installation">無需手動安裝</string>
112 <string name="prioritized_support">優先支援</string>
113 <string name="helping_game_preservation">協助遊戲保留</string>
114 <string name="our_eternal_gratitude">我們永遠的感激</string>
115 <string name="are_you_interested">您仍感興趣嗎?</string>
116
117 <!-- General settings strings -->
118 <string name="frame_limit_enable">啟用限制速度</string>
119 <string name="frame_limit_enable_description">若啟用,模擬速度將會限制在標準速度的指定百分比。</string>
120 <string name="frame_limit_slider">限制速度百分比</string>
121 <string name="frame_limit_slider_description">指定限制模擬速度的百分比。預設為 100%,模擬速度將被限制為標準速度。更高或更低的值將會增加或減少速度限制。</string>
122 <string name="cpu_accuracy">CPU 準確度</string>
123
124 <!-- System settings strings -->
125 <string name="use_docked_mode">底座模式</string>
126 <string name="use_docked_mode_description">以底座模式模擬,以犧牲效能的代價提高解析度。</string>
127 <string name="emulated_region">模擬區域</string>
128 <string name="emulated_language">模擬語言</string>
129 <string name="select_rtc_date">選取 RTC 日期</string>
130 <string name="select_rtc_time">選取 RTC 時間</string>
131 <string name="use_custom_rtc">啟用自訂 RTC</string>
132 <string name="use_custom_rtc_description">此設定允許您設定與您的目前系統時間相互獨立的自訂即時時鐘</string>
133 <string name="set_custom_rtc">設定自訂 RTC</string>
134
135 <!-- Graphics settings strings -->
136 <string name="renderer_api">API</string>
137 <string name="renderer_accuracy">準確度層級</string>
138 <string name="renderer_resolution">解析度</string>
139 <string name="renderer_vsync">VSync 模式</string>
140 <string name="renderer_aspect_ratio">長寬比</string>
141 <string name="renderer_scaling_filter">視窗適應過濾器</string>
142 <string name="renderer_anti_aliasing">消除鋸齒方法</string>
143 <string name="renderer_force_max_clock">強制最大時脈 (僅 Adreno)</string>
144 <string name="renderer_force_max_clock_description">強制 GPU 以最大可能時脈執行 (熱溫限制仍被套用)。</string>
145 <string name="renderer_asynchronous_shaders">使用非同步著色器</string>
146 <string name="renderer_asynchronous_shaders_description">非同步編譯著色器,將會減少間斷,但可能會引入故障。</string>
147 <string name="renderer_debug">啟用圖形偵錯</string>
148 <string name="renderer_debug_description">核取時,圖形 API 將會進入慢速偵錯模式。</string>
149 <string name="use_disk_shader_cache">使用磁碟著色器快取</string>
150 <string name="use_disk_shader_cache_description">透過將產生的著色器儲存並載入至磁碟,減少中斷。</string>
151
152 <!-- Audio settings strings -->
153 <string name="audio_volume">音量</string>
154 <string name="audio_volume_description">指定音訊輸出音量。</string>
155
156 <!-- Miscellaneous -->
157 <string name="slider_default">預設</string>
158 <string name="ini_saved">已儲存設定</string>
159 <string name="gameid_saved">已儲存 %1$s 設定</string>
160 <string name="error_saving">儲存 %1$s 時發生錯誤 ini: %2$s</string>
161 <string name="loading">正在載入…</string>
162 <string name="reset_setting_confirmation">要將此設定重設回預設值嗎?</string>
163 <string name="reset_to_default">重設為預設值</string>
164 <string name="reset_all_settings">重設所有設定?</string>
165 <string name="reset_all_settings_description">所有進階設定將被重設為預設組態,此動作無法復原。</string>
166 <string name="settings_reset">設定已重設</string>
167 <string name="close">關閉</string>
168 <string name="learn_more">深入瞭解</string>
169
170 <!-- GPU driver installation -->
171 <string name="select_gpu_driver">選取 GPU 驅動程式</string>
172 <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
173 <string name="select_gpu_driver_install">安裝</string>
174 <string name="select_gpu_driver_default">預設</string>
175 <string name="select_gpu_driver_install_success">已安裝 %s</string>
176 <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
177 <string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
178 <string name="system_gpu_driver">系統 GPU 驅動程式</string>
179 <string name="installing_driver">正在安裝驅動程式…</string>
180
181 <!-- Preferences Screen -->
182 <string name="preferences_settings">設定</string>
183 <string name="preferences_general">一般</string>
184 <string name="preferences_system">系統</string>
185 <string name="preferences_graphics">圖形</string>
186 <string name="preferences_audio">音訊</string>
187 <string name="preferences_theme">主題和色彩</string>
188
189 <!-- ROM loading errors -->
190 <string name="loader_error_encrypted">您的 ROM 已加密</string>
191 <string name="loader_error_encrypted_roms_description"><![CDATA[請依循指南重新傾印您的<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">遊戲卡匣</a>或<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">安裝標題</a>。]]></string>
192 <string name="loader_error_encrypted_keys_description"><![CDATA[請確保您的 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> 檔案已安裝,讓遊戲可以解密。]]></string>
193 <string name="loader_error_video_core">初始化視訊核心時發生錯誤</string>
194 <string name="loader_error_video_core_description">這經常由不相容的 GPU 驅動程式造成,安裝自訂 GPU 驅動程式可能會解決此問題。</string>
195 <string name="loader_error_invalid_format">無法載入 ROM</string>
196 <string name="loader_error_file_not_found">ROM 檔案不存在</string>
197
198 <!-- Emulation Menu -->
199 <string name="emulation_exit">結束模擬</string>
200 <string name="emulation_done">完成</string>
201 <string name="emulation_fps_counter">FPS 計數器</string>
202 <string name="emulation_toggle_controls">切換控制</string>
203 <string name="emulation_rel_stick_center">相對搖桿中心</string>
204 <string name="emulation_dpad_slide">方向鍵滑動</string>
205 <string name="emulation_haptics">觸覺回饋技術</string>
206 <string name="emulation_show_overlay">顯示覆疊</string>
207 <string name="emulation_toggle_all">全部切換</string>
208 <string name="emulation_control_adjust">調整覆疊</string>
209 <string name="emulation_control_scale">縮放</string>
210 <string name="emulation_control_opacity">不透明度</string>
211 <string name="emulation_touch_overlay_reset">重設覆疊</string>
212 <string name="emulation_touch_overlay_edit">編輯覆疊</string>
213 <string name="emulation_pause">暫停模擬</string>
214 <string name="emulation_unpause">取消暫停模擬</string>
215 <string name="emulation_input_overlay">覆疊選項</string>
216 <string name="emulation_game_loading">遊戲正在載入…</string>
217
218 <string name="load_settings">正在載入設定…</string>
219
220 <!-- Software keyboard -->
221 <string name="software_keyboard">軟體鍵盤</string>
222
223 <!-- Errors and warnings -->
224 <string name="abort_button">中止</string>
225 <string name="continue_button">繼續</string>
226 <string name="system_archive_not_found">找不到系統檔案</string>
227 <string name="system_archive_not_found_message">%s 遺失,請傾印您的系統封存。\n繼續模擬可能會造成當機和錯誤。</string>
228 <string name="system_archive_general">系統封存</string>
229 <string name="save_load_error">儲存/載入發生錯誤</string>
230 <string name="fatal_error">嚴重錯誤</string>
231 <string name="fatal_error_message">發生嚴重錯誤,檢查記錄以取得詳細資訊。\n繼續模擬可能會造成當機和錯誤。</string>
232 <string name="performance_warning">關閉此設定會顯著降低模擬效能!如需最佳體驗,建議您將此設定保持為啟用狀態。</string>
233
234 <!-- Region Names -->
235 <string name="region_japan">日本</string>
236 <string name="region_usa">美國</string>
237 <string name="region_europe">歐洲</string>
238 <string name="region_australia">澳洲</string>
239 <string name="region_china">中國</string>
240 <string name="region_korea">南韓</string>
241 <string name="region_taiwan">台灣</string>
242
243 <!-- Language Names -->
244 <string name="language_japanese">日文 (日本語)</string>
245 <string name="language_english">英文</string>
246 <string name="language_french">法文 (Français)</string>
247 <string name="langauge_german">德文 (Deutsch)</string>
248 <string name="language_italian">義大利文 (Italiano)</string>
249 <string name="language_spanish">西班牙文 (Español)</string>
250 <string name="language_chinese">中文 (简体中文)</string>
251 <string name="language_korean">韓文 (한국어)</string>
252 <string name="language_dutch">荷蘭文 (Nederlands)</string>
253 <string name="language_portuguese">葡萄牙文 (Português)</string>
254 <string name="language_russian">俄文 (Русский)</string>
255 <string name="language_taiwanese">台文 (台灣)</string>
256 <string name="language_british_english">英式英文</string>
257 <string name="language_canadian_french">加拿大法文 (Français canadien)</string>
258 <string name="language_latin_american_spanish">拉丁美洲西班牙文 (Español latinoamericano)</string>
259 <string name="language_simplified_chinese">簡體中文 (简体中文)</string>
260 <string name="language_traditional_chinese">正體中文 (正體中文)</string>
261 <string name="language_brazilian_portuguese">巴西葡萄牙文 (Português do Brasil)</string>
262
263 <!-- Renderer APIs -->
264 <string name="renderer_vulkan">Vulkan</string>
265 <string name="renderer_none">無</string>
266
267 <!-- Renderer Accuracy -->
268 <string name="renderer_accuracy_normal">標準</string>
269 <string name="renderer_accuracy_high">高</string>
270 <string name="renderer_accuracy_extreme">極高 (慢)</string>
271
272 <!-- Resolutions -->
273 <string name="resolution_half">0.5X (360p/540p)</string>
274 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
275 <string name="resolution_one">1X (720p/1080p)</string>
276 <string name="resolution_two">2X (1440p/2160p) (慢)</string>
277 <string name="resolution_three">3X (2160p/3240p) (慢)</string>
278 <string name="resolution_four">4X (2880p/4320p) (慢)</string>
279
280 <!-- Renderer VSync -->
281 <string name="renderer_vsync_immediate">即時 (關閉)</string>
282 <string name="renderer_vsync_mailbox">信箱</string>
283 <string name="renderer_vsync_fifo">FIFO (開啟)</string>
284 <string name="renderer_vsync_fifo_relaxed">FIFO 寬鬆</string>
285
286 <!-- Scaling Filters -->
287 <string name="scaling_filter_nearest_neighbor">最近鄰</string>
288 <string name="scaling_filter_bilinear">雙線性</string>
289 <string name="scaling_filter_bicubic">雙立方</string>
290 <string name="scaling_filter_gaussian">高斯</string>
291 <string name="scaling_filter_scale_force">強制縮放</string>
292 <string name="scaling_filter_fsr">AMD Radeon™ 超級解析度</string>
293
294 <!-- Anti-Aliasing -->
295 <string name="anti_aliasing_none">無</string>
296 <string name="anti_aliasing_fxaa">FXAA</string>
297 <string name="anti_aliasing_smaa">SMAA</string>
298
299 <!-- Aspect Ratios -->
300 <string name="ratio_default">預設 (16:9)</string>
301 <string name="ratio_force_four_three">強制 4:3</string>
302 <string name="ratio_force_twenty_one_nine">強制 21:9</string>
303 <string name="ratio_force_sixteen_ten">強制 16:10</string>
304 <string name="ratio_stretch">延伸視窗</string>
305
306 <!-- CPU Accuracy -->
307 <string name="cpu_accuracy_unsafe">低精度</string>
308 <string name="cpu_accuracy_paranoid">不合理 (慢)</string>
309
310 <!-- Gamepad Buttons -->
311 <string name="gamepad_d_pad">方向鍵</string>
312 <string name="gamepad_left_stick">左搖桿</string>
313 <string name="gamepad_right_stick">右搖桿</string>
314 <string name="gamepad_home">HOME</string>
315 <string name="gamepad_screenshot">螢幕截圖</string>
316
317 <!-- Disk shader cache -->
318 <string name="preparing_shaders">正在準備著色器</string>
319 <string name="building_shaders">正在建置著色器</string>
320
321 <!-- Theme options -->
322 <string name="change_app_theme">變更應用程式主題</string>
323 <string name="theme_default">預設</string>
324 <string name="theme_material_you">Material You</string>
325
326 <!-- Theme Modes -->
327 <string name="change_theme_mode">變更主題模式</string>
328 <string name="theme_mode_follow_system">跟隨系統</string>
329 <string name="theme_mode_light">淺色</string>
330 <string name="theme_mode_dark">深色</string>
331
332 <!-- Black backgrounds theme -->
333 <string name="use_black_backgrounds">使用黑色背景</string>
334 <string name="use_black_backgrounds_description">使用深色主題時,套用黑色背景。</string>
335
336</resources>
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
new file mode 100644
index 000000000..ea20cb17c
--- /dev/null
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -0,0 +1,227 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <string-array name="regionNames">
5 <item>@string/auto</item>
6 <item>@string/region_australia</item>
7 <item>@string/region_china</item>
8 <item>@string/region_europe</item>
9 <item>@string/region_japan</item>
10 <item>@string/region_korea</item>
11 <item>@string/region_taiwan</item>
12 <item>@string/region_usa</item>
13 </string-array>
14
15 <integer-array name="regionValues">
16 <item>-1</item>
17 <item>3</item>
18 <item>4</item>
19 <item>2</item>
20 <item>0</item>
21 <item>5</item>
22 <item>6</item>
23 <item>1</item>
24 </integer-array>
25
26 <string-array name="languageNames">
27 <item>@string/language_brazilian_portuguese</item>
28 <item>@string/language_british_english</item>
29 <item>@string/language_canadian_french</item>
30 <item>@string/language_chinese</item>
31 <item>@string/language_dutch</item>
32 <item>@string/language_english</item>
33 <item>@string/language_french</item>
34 <item>@string/langauge_german</item>
35 <item>@string/language_italian</item>
36 <item>@string/language_japanese</item>
37 <item>@string/language_korean</item>
38 <item>@string/language_latin_american_spanish</item>
39 <item>@string/language_portuguese</item>
40 <item>@string/language_russian</item>
41 <item>@string/language_simplified_chinese</item>
42 <item>@string/language_spanish</item>
43 <item>@string/language_taiwanese</item>
44 <item>@string/language_traditional_chinese</item>
45 </string-array>
46
47 <integer-array name="languageValues">
48 <item>17</item>
49 <item>12</item>
50 <item>13</item>
51 <item>6</item>
52 <item>8</item>
53 <item>1</item>
54 <item>2</item>
55 <item>3</item>
56 <item>4</item>
57 <item>0</item>
58 <item>7</item>
59 <item>14</item>
60 <item>9</item>
61 <item>10</item>
62 <item>15</item>
63 <item>5</item>
64 <item>11</item>
65 <item>16</item>
66 </integer-array>
67
68 <string-array name="rendererApiNames">
69 <item>@string/renderer_vulkan</item>
70 <item>@string/renderer_none</item>
71 </string-array>
72
73 <integer-array name="rendererApiValues">
74 <item>1</item>
75 <item>2</item>
76 </integer-array>
77
78 <string-array name="rendererAccuracyNames">
79 <item>@string/renderer_accuracy_normal</item>
80 <item>@string/renderer_accuracy_high</item>
81 <item>@string/renderer_accuracy_extreme</item>
82 </string-array>
83
84 <integer-array name="rendererAccuracyValues">
85 <item>0</item>
86 <item>1</item>
87 <item>2</item>
88 </integer-array>
89
90 <string-array name="rendererResolutionNames">
91 <item>@string/resolution_half</item>
92 <item>@string/resolution_three_quarter</item>
93 <item>@string/resolution_one</item>
94 <item>@string/resolution_two</item>
95 <item>@string/resolution_three</item>
96 <item>@string/resolution_four</item>
97 </string-array>
98
99 <string-array name="rendererVSyncNames">
100 <item>@string/renderer_vsync_immediate</item>
101 <item>@string/renderer_vsync_mailbox</item>
102 <item>@string/renderer_vsync_fifo</item>
103 <item>@string/renderer_vsync_fifo_relaxed</item>
104 </string-array>
105
106 <integer-array name="rendererResolutionValues">
107 <item>0</item>
108 <item>1</item>
109 <item>2</item>
110 <item>3</item>
111 <item>4</item>
112 <item>5</item>
113 </integer-array>
114
115 <integer-array name="rendererVSyncValues">
116 <item>0</item>
117 <item>1</item>
118 <item>2</item>
119 <item>3</item>
120 </integer-array>
121
122 <string-array name="rendererAspectRatioNames">
123 <item>@string/ratio_default</item>
124 <item>@string/ratio_force_four_three</item>
125 <item>@string/ratio_force_twenty_one_nine</item>
126 <item>@string/ratio_force_sixteen_ten</item>
127 <item>@string/ratio_stretch</item>
128 </string-array>
129
130 <integer-array name="rendererAspectRatioValues">
131 <item>0</item>
132 <item>1</item>
133 <item>2</item>
134 <item>3</item>
135 <item>4</item>
136 </integer-array>
137
138 <string-array name="rendererScalingFilterNames">
139 <item>@string/scaling_filter_nearest_neighbor</item>
140 <item>@string/scaling_filter_bilinear</item>
141 <item>@string/scaling_filter_bicubic</item>
142 <item>@string/scaling_filter_gaussian</item>
143 <item>@string/scaling_filter_scale_force</item>
144 <item>@string/scaling_filter_fsr</item>
145 </string-array>
146
147 <integer-array name="rendererScalingFilterValues">
148 <item>0</item>
149 <item>1</item>
150 <item>2</item>
151 <item>3</item>
152 <item>4</item>
153 <item>5</item>
154 </integer-array>
155
156 <string-array name="rendererAntiAliasingNames">
157 <item>@string/anti_aliasing_none</item>
158 <item>@string/anti_aliasing_fxaa</item>
159 <item>@string/anti_aliasing_smaa</item>
160 </string-array>
161
162 <integer-array name="rendererAntiAliasingValues">
163 <item>0</item>
164 <item>1</item>
165 <item>2</item>
166 </integer-array>
167
168 <string-array name="cpuAccuracyNames">
169 <item>@string/auto</item>
170 <item>@string/cpu_accuracy_accurate</item>
171 <item>@string/cpu_accuracy_unsafe</item>
172 <item>@string/cpu_accuracy_paranoid</item>
173 </string-array>
174
175 <integer-array name="cpuAccuracyValues">
176 <item>0</item>
177 <item>1</item>
178 <item>2</item>
179 <item>3</item>
180 </integer-array>
181
182 <string-array name="gamepadButtons">
183 <item>A</item>
184 <item>B</item>
185 <item>X</item>
186 <item>Y</item>
187 <item>L</item>
188 <item>R</item>
189 <item>ZL</item>
190 <item>ZR</item>
191 <item>+</item>
192 <item>-</item>
193 <item>@string/gamepad_d_pad</item>
194 <item>@string/gamepad_left_stick</item>
195 <item>@string/gamepad_right_stick</item>
196 <item>@string/gamepad_home</item>
197 <item>@string/gamepad_screenshot</item>
198 </string-array>
199
200 <string-array name="themeEntries">
201 <item>@string/theme_default</item>
202 </string-array>
203 <integer-array name="themeValues">
204 <item>0</item>
205 </integer-array>
206
207 <string-array name="themeEntriesA12">
208 <item>@string/theme_default</item>
209 <item>@string/theme_material_you</item>
210 </string-array>
211 <integer-array name="themeValuesA12">
212 <item>0</item>
213 <item>1</item>
214 </integer-array>
215
216 <string-array name="themeModeEntries">
217 <item>@string/theme_mode_follow_system</item>
218 <item>@string/theme_mode_light</item>
219 <item>@string/theme_mode_dark</item>
220 </string-array>
221 <integer-array name="themeModeValues">
222 <item>-1</item>
223 <item>1</item>
224 <item>2</item>
225 </integer-array>
226
227</resources>
diff --git a/src/android/app/src/main/res/values/bools.xml b/src/android/app/src/main/res/values/bools.xml
new file mode 100644
index 000000000..e50f473fb
--- /dev/null
+++ b/src/android/app/src/main/res/values/bools.xml
@@ -0,0 +1,4 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3 <bool name="small_layout">true</bool>
4</resources>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..00757e5e8
--- /dev/null
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -0,0 +1,18 @@
1<resources>
2 <dimen name="spacing_small">4dp</dimen>
3 <dimen name="spacing_med">8dp</dimen>
4 <dimen name="spacing_medlarge">12dp</dimen>
5 <dimen name="spacing_large">16dp</dimen>
6 <dimen name="spacing_xtralarge">32dp</dimen>
7 <dimen name="spacing_list">64dp</dimen>
8 <dimen name="spacing_chip">20dp</dimen>
9 <dimen name="spacing_navigation">80dp</dimen>
10 <dimen name="spacing_navigation_rail">0dp</dimen>
11 <dimen name="spacing_search">128dp</dimen>
12 <dimen name="spacing_refresh_end">72dp</dimen>
13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen>
15
16 <dimen name="dialog_margin">20dp</dimen>
17 <dimen name="elevated_app_bar">3dp</dimen>
18</resources>
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
new file mode 100644
index 000000000..bc614b81d
--- /dev/null
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3 <integer name="game_title_lines">2</integer>
4
5 <!-- Default SWITCH landscape layout -->
6 <integer name="SWITCH_BUTTON_A_X">760</integer>
7 <integer name="SWITCH_BUTTON_A_Y">790</integer>
8 <integer name="SWITCH_BUTTON_B_X">710</integer>
9 <integer name="SWITCH_BUTTON_B_Y">900</integer>
10 <integer name="SWITCH_BUTTON_X_X">710</integer>
11 <integer name="SWITCH_BUTTON_X_Y">680</integer>
12 <integer name="SWITCH_BUTTON_Y_X">660</integer>
13 <integer name="SWITCH_BUTTON_Y_Y">790</integer>
14 <integer name="SWITCH_STICK_L_X">100</integer>
15 <integer name="SWITCH_STICK_L_Y">670</integer>
16 <integer name="SWITCH_STICK_R_X">900</integer>
17 <integer name="SWITCH_STICK_R_Y">670</integer>
18 <integer name="SWITCH_TRIGGER_L_X">70</integer>
19 <integer name="SWITCH_TRIGGER_L_Y">220</integer>
20 <integer name="SWITCH_TRIGGER_R_X">930</integer>
21 <integer name="SWITCH_TRIGGER_R_Y">220</integer>
22 <integer name="SWITCH_TRIGGER_ZL_X">70</integer>
23 <integer name="SWITCH_TRIGGER_ZL_Y">90</integer>
24 <integer name="SWITCH_TRIGGER_ZR_X">930</integer>
25 <integer name="SWITCH_TRIGGER_ZR_Y">90</integer>
26 <integer name="SWITCH_BUTTON_MINUS_X">460</integer>
27 <integer name="SWITCH_BUTTON_MINUS_Y">950</integer>
28 <integer name="SWITCH_BUTTON_PLUS_X">540</integer>
29 <integer name="SWITCH_BUTTON_PLUS_Y">950</integer>
30 <integer name="SWITCH_BUTTON_HOME_X">600</integer>
31 <integer name="SWITCH_BUTTON_HOME_Y">950</integer>
32 <integer name="SWITCH_BUTTON_CAPTURE_X">400</integer>
33 <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer>
34 <integer name="SWITCH_BUTTON_DPAD_X">260</integer>
35 <integer name="SWITCH_BUTTON_DPAD_Y">790</integer>
36
37</resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..c236811fa
--- /dev/null
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,874 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <!-- General application strings -->
5 <string name="app_name" translatable="false">yuzu</string>
6 <string name="app_disclaimer">This software will run games for the Nintendo Switch game console. No game titles or keys are included.&lt;br /&gt;&lt;br /&gt;Before you begin, please locate your <![CDATA[<b> prod.keys </b>]]> file on your device storage.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href="https://yuzu-emu.org/help/quickstart">Learn more</a>]]></string>
7 <string name="emulation_notification_channel_name">Emulation is Active</string>
8 <string name="emulation_notification_channel_id" translatable="false">emulationIsActive</string>
9 <string name="emulation_notification_channel_description">Shows a persistent notification when emulation is running.</string>
10 <string name="emulation_notification_running">yuzu is running</string>
11 <string name="notice_notification_channel_name">Notices and errors</string>
12 <string name="notice_notification_channel_id" translatable="false">noticesAndErrors</string>
13 <string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string>
14 <string name="notification_permission_not_granted">Notification permission not granted!</string>
15
16 <!-- Setup strings -->
17 <string name="welcome">Welcome!</string>
18 <string name="welcome_description">Learn how to setup &lt;b>yuzu&lt;/b> and jump into emulation.</string>
19 <string name="get_started">Get started</string>
20 <string name="keys">Keys</string>
21 <string name="keys_description">Select your &lt;b>prod.keys&lt;/b> file with the button below.</string>
22 <string name="select_keys">Select Keys</string>
23 <string name="games">Games</string>
24 <string name="games_description">Select your &lt;b>Games&lt;/b> folder with the button below.</string>
25 <string name="done">Done</string>
26 <string name="done_description">You\'re all set.\nEnjoy your games!</string>
27 <string name="text_continue">Continue</string>
28 <string name="next">Next</string>
29 <string name="back">Back</string>
30 <string name="add_games">Add Games</string>
31 <string name="add_games_description">Select your games folder</string>
32
33 <!-- Home strings -->
34 <string name="home_games">Games</string>
35 <string name="home_search">Search</string>
36 <string name="home_settings">Settings</string>
37 <string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
38 <string name="search_and_filter_games">Search and filter games</string>
39 <string name="select_games_folder">Select games folder</string>
40 <string name="select_games_folder_description">Allows yuzu to populate the games list</string>
41 <string name="add_games_warning">Skip selecting games folder?</string>
42 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
43 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
44 <string name="home_search_games">Search games</string>
45 <string name="games_dir_selected">Games directory selected</string>
46 <string name="install_prod_keys">Install prod.keys</string>
47 <string name="install_prod_keys_description">Required to decrypt retail games</string>
48 <string name="install_prod_keys_warning">Skip adding keys?</string>
49 <string name="install_prod_keys_warning_description">Valid keys are required to emulate retail games. Only homebrew apps will function if you continue.</string>
50 <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
51 <string name="notifications">Notifications</string>
52 <string name="notifications_description">Grant the notification permission with the button below.</string>
53 <string name="give_permission">Grant permission</string>
54 <string name="notification_warning">Skip granting the notification permission?</string>
55 <string name="notification_warning_description">yuzu won\'t be able to notify you of important information.</string>
56 <string name="permission_denied">Permission denied</string>
57 <string name="permission_denied_description">You denied this permission too many times and now you have to manually grant it in system settings.</string>
58 <string name="about">About</string>
59 <string name="about_description">Build version, credits, and more</string>
60 <string name="warning_help">Help</string>
61 <string name="warning_skip">Skip</string>
62 <string name="warning_cancel">Cancel</string>
63 <string name="install_amiibo_keys">Install Amiibo keys</string>
64 <string name="install_amiibo_keys_description">Required to use Amiibo in game</string>
65 <string name="invalid_keys_file">Invalid keys file selected</string>
66 <string name="install_keys_success">Keys successfully installed</string>
67 <string name="reading_keys_failure">Error reading encryption keys</string>
68 <string name="install_prod_keys_failure_extension_description">Verify your keys file has a .keys extension and try again.</string>
69 <string name="install_amiibo_keys_failure_extension_description">Verify your keys file has a .bin extension and try again.</string>
70 <string name="invalid_keys_error">Invalid encryption keys</string>
71 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
72 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
73 <string name="install_gpu_driver">Install GPU driver</string>
74 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
75 <string name="advanced_settings">Advanced settings</string>
76 <string name="settings_description">Configure emulator settings</string>
77 <string name="search_recently_played">Recently played</string>
78 <string name="search_recently_added">Recently added</string>
79 <string name="search_retail">Retail</string>
80 <string name="search_homebrew">Homebrew</string>
81 <string name="open_user_folder">Open yuzu folder</string>
82 <string name="open_user_folder_description">Manage yuzu\'s internal files</string>
83 <string name="theme_and_color_description">Modify the look of the app</string>
84 <string name="no_file_manager">No file manager found</string>
85 <string name="notification_no_directory_link">Could not open yuzu directory</string>
86 <string name="notification_no_directory_link_description">Please locate the user folder with the file manager\'s side panel manually.</string>
87 <string name="manage_save_data">Manage save data</string>
88 <string name="manage_save_data_description">Save data found. Please select an option below.</string>
89 <string name="import_export_saves_description">Import or export save files</string>
90 <string name="import_export_saves_no_profile">No save data found. Please launch a game and retry.</string>
91 <string name="save_file_imported_success">Imported successfully</string>
92 <string name="save_file_invalid_zip_structure">Invalid save directory structure</string>
93 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
94 <string name="import_saves">Import</string>
95 <string name="export_saves">Export</string>
96 <string name="install_firmware">Install firmware</string>
97 <string name="install_firmware_description">Firmware must be in a ZIP archive and is needed to boot some games</string>
98 <string name="firmware_installing">Installing firmware</string>
99 <string name="firmware_installed_success">Firmware installed successfully</string>
100 <string name="firmware_installed_failure">Firmware installation failed</string>
101 <string name="firmware_installed_failure_description">Verify that the ZIP contains valid firmware and try again.</string>
102 <string name="share_log">Share debug logs</string>
103 <string name="share_log_description">Share yuzu\'s log file to debug issues</string>
104 <string name="share_log_missing">No log file found</string>
105 <string name="install_game_content">Install game content</string>
106 <string name="install_game_content_description">Install game updates or DLC</string>
107 <string name="install_game_content_failure">Error installing file to NAND</string>
108 <string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string>
109 <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string>
110 <string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string>
111 <string name="install_game_content_success">Game content installed successfully</string>
112 <string name="install_game_content_success_overwrite">Game content was overwritten successfully</string>
113 <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
114
115 <!-- About screen strings -->
116 <string name="gaia_is_not_real">Gaia isn\'t real</string>
117 <string name="copied_to_clipboard">Copied to clipboard</string>
118 <string name="about_app_description">An open-source Switch emulator</string>
119 <string name="contributors">Contributors</string>
120 <string name="contributors_description">Made with \u2764 from the yuzu team</string>
121 <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
122 <string name="licenses_description">Projects that make yuzu for Android possible</string>
123 <string name="build">Build</string>
124 <string name="support_link">https://discord.gg/u77vRWY</string>
125 <string name="website_link">https://yuzu-emu.org/</string>
126 <string name="github_link">https://github.com/yuzu-emu</string>
127
128 <!-- Early access upgrade strings -->
129 <string name="early_access">Early Access</string>
130 <string name="get_early_access">Get Early Access</string>
131 <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
132 <string name="get_early_access_description">Cutting-edge features, early access to updates, and more</string>
133 <string name="early_access_benefits">Early Access Benefits</string>
134 <string name="cutting_edge_features">Cutting-edge features</string>
135 <string name="early_access_updates">Early access to updates</string>
136 <string name="no_manual_installation">No manual installation</string>
137 <string name="prioritized_support">Prioritized support</string>
138 <string name="helping_game_preservation">Helping game preservation</string>
139 <string name="our_eternal_gratitude">Our eternal gratitude</string>
140 <string name="are_you_interested">Are you interested?</string>
141
142 <!-- General settings strings -->
143 <string name="frame_limit_enable">Limit speed</string>
144 <string name="frame_limit_enable_description">Limits emulation speed to a specified percentage of normal speed.</string>
145 <string name="frame_limit_slider">Limit speed percent</string>
146 <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string>
147 <string name="cpu_accuracy">CPU accuracy</string>
148
149 <!-- System settings strings -->
150 <string name="use_docked_mode">Docked Mode</string>
151 <string name="use_docked_mode_description">Increases resolution, decreasing performance. Handheld Mode is used when disabled, lowering resolution and increasing performance.</string>
152 <string name="emulated_region">Emulated region</string>
153 <string name="emulated_language">Emulated language</string>
154 <string name="select_rtc_date">Select RTC date</string>
155 <string name="select_rtc_time">Select RTC time</string>
156 <string name="use_custom_rtc">Custom RTC</string>
157 <string name="use_custom_rtc_description">Allows you to set a custom real-time clock separate from your current system time.</string>
158 <string name="set_custom_rtc">Set custom RTC</string>
159
160 <!-- Graphics settings strings -->
161 <string name="renderer_api">API</string>
162 <string name="renderer_accuracy">Accuracy level</string>
163 <string name="renderer_resolution">Resolution (Handheld/Docked)</string>
164 <string name="renderer_vsync">VSync mode</string>
165 <string name="renderer_aspect_ratio">Aspect ratio</string>
166 <string name="renderer_scaling_filter">Window adapting filter</string>
167 <string name="renderer_anti_aliasing">Anti-aliasing method</string>
168 <string name="renderer_force_max_clock">Force maximum clocks (Adreno only)</string>
169 <string name="renderer_force_max_clock_description">Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).</string>
170 <string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
171 <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</string>
172 <string name="renderer_reactive_flushing">Use reactive flushing</string>
173 <string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
174 <string name="renderer_debug">Graphics debugging</string>
175 <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string>
176 <string name="use_disk_shader_cache">Disk shader cache</string>
177 <string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string>
178
179 <!-- Audio settings strings -->
180 <string name="audio_volume">Volume</string>
181 <string name="audio_volume_description">Specifies the volume of audio output.</string>
182
183 <!-- Miscellaneous -->
184 <string name="slider_default">Default</string>
185 <string name="ini_saved">Saved settings</string>
186 <string name="gameid_saved">Saved settings for %1$s</string>
187 <string name="error_saving">Error saving %1$s.ini: %2$s</string>
188 <string name="loading">Loading…</string>
189 <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
190 <string name="reset_to_default">Reset to default</string>
191 <string name="reset_all_settings">Reset all settings?</string>
192 <string name="reset_all_settings_description">All advanced settings will be reset to their default configuration. This can not be undone.</string>
193 <string name="settings_reset">Settings reset</string>
194 <string name="close">Close</string>
195 <string name="learn_more">Learn more</string>
196 <string name="auto">Auto</string>
197 <string name="submit">Submit</string>
198
199 <!-- GPU driver installation -->
200 <string name="select_gpu_driver">Select GPU driver</string>
201 <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
202 <string name="select_gpu_driver_install">Install</string>
203 <string name="select_gpu_driver_default">Default</string>
204 <string name="select_gpu_driver_install_success">Installed %s</string>
205 <string name="select_gpu_driver_use_default">Using default GPU driver</string>
206 <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string>
207 <string name="system_gpu_driver">System GPU driver</string>
208 <string name="installing_driver">Installing driver…</string>
209
210 <!-- Preferences Screen -->
211 <string name="preferences_settings">Settings</string>
212 <string name="preferences_general">General</string>
213 <string name="preferences_system">System</string>
214 <string name="preferences_graphics">Graphics</string>
215 <string name="preferences_audio">Audio</string>
216 <string name="preferences_theme">Theme and color</string>
217 <string name="preferences_debug">Debug</string>
218
219 <!-- ROM loading errors -->
220 <string name="loader_error_encrypted">Your ROM is encrypted</string>
221 <string name="loader_error_encrypted_roms_description"><![CDATA[Please follow the guides to redump your <a href="https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games">game cartidges</a> or <a href="https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop">installed titles</a>.]]></string>
222 <string name="loader_error_encrypted_keys_description"><![CDATA[Please ensure your <a href="https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys">prod.keys</a> file is installed so that games can be decrypted.]]></string>
223 <string name="loader_error_video_core">An error occurred initializing the video core</string>
224 <string name="loader_error_video_core_description">This is usually caused by an incompatible GPU driver. Installing a custom GPU driver may resolve this problem.</string>
225 <string name="loader_error_invalid_format">Unable to load ROM</string>
226 <string name="loader_error_file_not_found">ROM file does not exist</string>
227
228 <!-- Emulation Menu -->
229 <string name="emulation_exit">Exit emulation</string>
230 <string name="emulation_done">Done</string>
231 <string name="emulation_fps_counter">FPS counter</string>
232 <string name="emulation_toggle_controls">Toggle controls</string>
233 <string name="emulation_rel_stick_center">Relative stick center</string>
234 <string name="emulation_dpad_slide">D-pad slide</string>
235 <string name="emulation_haptics">Touch haptics</string>
236 <string name="emulation_show_overlay">Show overlay</string>
237 <string name="emulation_toggle_all">Toggle all</string>
238 <string name="emulation_control_adjust">Adjust overlay</string>
239 <string name="emulation_control_scale">Scale</string>
240 <string name="emulation_control_opacity">Opacity</string>
241 <string name="emulation_touch_overlay_reset">Reset overlay</string>
242 <string name="emulation_touch_overlay_edit">Edit overlay</string>
243 <string name="emulation_pause">Pause emulation</string>
244 <string name="emulation_unpause">Unpause emulation</string>
245 <string name="emulation_input_overlay">Overlay options</string>
246 <string name="emulation_game_loading">Game loading…</string>
247
248 <string name="load_settings">Loading settings…</string>
249
250 <!-- Software keyboard -->
251 <string name="software_keyboard">Software keyboard</string>
252
253 <!-- Errors and warnings -->
254 <string name="abort_button">Abort</string>
255 <string name="continue_button">Continue</string>
256 <string name="system_archive_not_found">System Archive Not Found</string>
257 <string name="system_archive_not_found_message">%s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs.</string>
258 <string name="system_archive_general">A system archive</string>
259 <string name="save_load_error">Save/Load Error</string>
260 <string name="fatal_error">Fatal Error</string>
261 <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
262 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
263
264 <!-- Region Names -->
265 <string name="region_japan">Japan</string>
266 <string name="region_usa">USA</string>
267 <string name="region_europe">Europe</string>
268 <string name="region_australia">Australia</string>
269 <string name="region_china">China</string>
270 <string name="region_korea">Korea</string>
271 <string name="region_taiwan">Taiwan</string>
272
273 <!-- Language Names -->
274 <string name="language_japanese">Japanese (日本語)</string>
275 <string name="language_english">English</string>
276 <string name="language_french">French (Français)</string>
277 <string name="langauge_german">German (Deutsch)</string>
278 <string name="language_italian">Italian (Italiano)</string>
279 <string name="language_spanish">Spanish (Español)</string>
280 <string name="language_chinese">Chinese (简体中文)</string>
281 <string name="language_korean">Korean (한국어)</string>
282 <string name="language_dutch">Dutch (Nederlands)</string>
283 <string name="language_portuguese">Portuguese (Português)</string>
284 <string name="language_russian">Russian (Русский)</string>
285 <string name="language_taiwanese">Taiwanese (台湾)</string>
286 <string name="language_british_english">British English</string>
287 <string name="language_canadian_french">Canadian French (Français canadien)</string>
288 <string name="language_latin_american_spanish">Latin American Spanish (Español latinoamericano)</string>
289 <string name="language_simplified_chinese">Simplified Chinese (简体中文)</string>
290 <string name="language_traditional_chinese">Traditional Chinese (正體中文)</string>
291 <string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string>
292
293 <!-- Renderer APIs -->
294 <string name="renderer_vulkan">Vulkan</string>
295 <string name="renderer_none">None</string>
296
297 <!-- Renderer Accuracy -->
298 <string name="renderer_accuracy_normal">Normal</string>
299 <string name="renderer_accuracy_high">High</string>
300 <string name="renderer_accuracy_extreme">Extreme (Slow)</string>
301
302 <!-- Resolutions -->
303 <string name="resolution_half">0.5X (360p/540p)</string>
304 <string name="resolution_three_quarter">0.75X (540p/810p)</string>
305 <string name="resolution_one">1X (720p/1080p)</string>
306 <string name="resolution_two">2X (1440p/2160p) (Slow)</string>
307 <string name="resolution_three">3X (2160p/3240p) (Slow)</string>
308 <string name="resolution_four">4X (2880p/4320p) (Slow)</string>
309
310 <!-- Renderer VSync -->
311 <string name="renderer_vsync_immediate">Immediate (Off)</string>
312 <string name="renderer_vsync_mailbox">Mailbox</string>
313 <string name="renderer_vsync_fifo">FIFO (On)</string>
314 <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
315
316 <!-- Scaling Filters -->
317 <string name="scaling_filter_nearest_neighbor">Nearest Neighbor</string>
318 <string name="scaling_filter_bilinear">Bilinear</string>
319 <string name="scaling_filter_bicubic">Bicubic</string>
320 <string name="scaling_filter_gaussian">Gaussian</string>
321 <string name="scaling_filter_scale_force">ScaleForce</string>
322 <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
323
324 <!-- Anti-Aliasing -->
325 <string name="anti_aliasing_none">None</string>
326 <string name="anti_aliasing_fxaa">FXAA</string>
327 <string name="anti_aliasing_smaa">SMAA</string>
328
329 <!-- Aspect Ratios -->
330 <string name="ratio_default">Default (16:9)</string>
331 <string name="ratio_force_four_three">Force 4:3</string>
332 <string name="ratio_force_twenty_one_nine">Force 21:9</string>
333 <string name="ratio_force_sixteen_ten">Force 16:10</string>
334 <string name="ratio_stretch">Stretch to window</string>
335
336 <!-- CPU Accuracy -->
337 <string name="cpu_accuracy_accurate">Accurate</string>
338 <string name="cpu_accuracy_unsafe">Unsafe</string>
339 <string name="cpu_accuracy_paranoid">Paranoid (Slow)</string>
340
341 <!-- Gamepad Buttons -->
342 <string name="gamepad_d_pad">D-pad</string>
343 <string name="gamepad_left_stick">Left stick</string>
344 <string name="gamepad_right_stick">Right stick</string>
345 <string name="gamepad_home">Home</string>
346 <string name="gamepad_screenshot">Screenshot</string>
347
348 <!-- Disk shader cache -->
349 <string name="preparing_shaders">Preparing shaders</string>
350 <string name="building_shaders">Building shaders</string>
351
352 <!-- Theme options -->
353 <string name="change_app_theme">Change app theme</string>
354 <string name="theme_default">Default</string>
355 <string name="theme_material_you">Material You</string>
356
357 <!-- Theme Modes -->
358 <string name="change_theme_mode">Change theme mode</string>
359 <string name="theme_mode_follow_system">Follow System</string>
360 <string name="theme_mode_light">Light</string>
361 <string name="theme_mode_dark">Dark</string>
362
363 <!-- Black backgrounds theme -->
364 <string name="use_black_backgrounds">Black backgrounds</string>
365 <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
366
367 <!-- Licenses screen strings -->
368 <string name="licenses">Licenses</string>
369 <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
370 <string name="license_fidelityfx_fsr_description">High-quality upscaling from AMD</string>
371 <string name="license_fidelityfx_fsr_link" translatable="false">https://github.com/GPUOpen-Effects/FidelityFX-FSR</string>
372 <string name="license_fidelityfx_fsr_copyright" translatable="false">Copyright © 2021 Advanced Micro Devices, Inc.</string>
373 <string name="license_fidelityfx_fsr_text" translatable="false">
374Permission is hereby granted, free of charge, to any person obtaining a copy
375of this software and associated documentation files (the \"Software"), to deal
376in the Software without restriction, including without limitation the rights
377to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
378copies of the Software, and to permit persons to whom the Software is
379furnished to do so, subject to the following conditions:\n\n
380
381The above copyright notice and this permission notice shall be included in
382all copies or substantial portions of the Software.\n\n
383
384THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
385IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
386FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
387AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
388LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
389OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
390THE SOFTWARE.
391 </string>
392 <string name="license_cubeb" translatable="false">cubeb</string>
393 <string name="license_cubeb_description" translatable="false">Cross platform audio library</string>
394 <string name="license_cubeb_link" translatable="false">https://github.com/mozilla/cubeb</string>
395 <string name="license_cubeb_copyright" translatable="false">Copyright © 2011 Mozilla Foundation</string>
396 <string name="license_cubeb_text" translatable="false">
397Permission to use, copy, modify, and distribute this software for any
398purpose with or without fee is hereby granted, provided that the above
399copyright notice and this permission notice appear in all copies.\n\n
400
401THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
402WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
403MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
404ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
405WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
406ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
407OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
408 </string>
409 <string name="license_dynarmic" translatable="false">Dynarmic</string>
410 <string name="license_dynarmic_description" translatable="false">An ARM dynamic recompiler</string>
411 <string name="license_dynarmic_link" translatable="false">https://github.com/merryhime/dynarmic</string>
412 <string name="license_dynarmic_copyright" translatable="false">Copyright © 2017 merryhime</string>
413 <string name="license_dynarmic_text" translatable="false">
414Permission to use, copy, modify, and/or distribute this software for
415any purpose with or without fee is hereby granted.\n\n
416
417THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
418WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
419MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
420ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
421WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
422AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
423OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
424 </string>
425 <string name="license_ffmpeg" translatable="false">FFmpeg</string>
426 <string name="license_ffmpeg_description" translatable="false">FFmpeg is a collection of libraries and tools to process multimedia content such as audio, video, subtitles and related metadata.</string>
427 <string name="license_ffmpeg_link" translatable="false">https://github.com/FFmpeg/FFmpeg</string>
428 <string name="license_ffmpeg_copyright" translatable="false">Copyright © 1991, 1999 Free Software Foundation, Inc.</string>
429 <string name="license_ffmpeg_text" translatable="false">
430GNU LESSER GENERAL PUBLIC LICENSE\n
431TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n
432
433 0. This License Agreement applies to any software library or other
434program which contains a notice placed by the copyright holder or
435other authorized party saying it may be distributed under the terms of
436this Lesser General Public License (also called \"this License\").
437Each licensee is addressed as \"you\".\n\n
438
439 A \"library\" means a collection of software functions and/or data
440prepared so as to be conveniently linked with application programs
441(which use some of those functions and data) to form executables.\n\n
442
443 The \"Library\", below, refers to any such software library or work
444which has been distributed under these terms. A \"work based on the
445Library\" means either the Library or any derivative work under
446copyright law: that is to say, a work containing the Library or a
447portion of it, either verbatim or with modifications and/or translated
448straightforwardly into another language. (Hereinafter, translation is
449included without limitation in the term \"modification\".)\n\n
450
451 \"Source code\" for a work means the preferred form of the work for
452making modifications to it. For a library, complete source code means
453all the source code for all modules it contains, plus any associated
454interface definition files, plus the scripts used to control compilation
455and installation of the library.\n\n
456
457 Activities other than copying, distribution and modification are not
458covered by this License; they are outside its scope. The act of
459running a program using the Library is not restricted, and output from
460such a program is covered only if its contents constitute a work based
461on the Library (independent of the use of the Library in a tool for
462writing it). Whether that is true depends on what the Library does
463and what the program that uses the Library does.\n\n
464
465 1. You may copy and distribute verbatim copies of the Library\'s
466complete source code as you receive it, in any medium, provided that
467you conspicuously and appropriately publish on each copy an
468appropriate copyright notice and disclaimer of warranty; keep intact
469all the notices that refer to this License and to the absence of any
470warranty; and distribute a copy of this License along with the
471Library.\n\n
472
473 You may charge a fee for the physical act of transferring a copy,
474and you may at your option offer warranty protection in exchange for a
475fee.\n\n
476
477 2. You may modify your copy or copies of the Library or any portion
478of it, thus forming a work based on the Library, and copy and
479distribute such modifications or work under the terms of Section 1
480above, provided that you also meet all of these conditions:\n\n
481
482a) The modified work must itself be a software library.\n\n
483
484b) You must cause the files modified to carry prominent notices
485stating that you changed the files and the date of any change.\n\n
486
487c) You must cause the whole of the work to be licensed at no
488charge to all third parties under the terms of this License.\n\n
489
490d) If a facility in the modified Library refers to a function or a
491table of data to be supplied by an application program that uses
492the facility, other than as an argument passed when the facility
493is invoked, then you must make a good faith effort to ensure that,
494in the event an application does not supply such function or
495table, the facility still operates, and performs whatever part of
496its purpose remains meaningful.\n\n
497
498(For example, a function in a library to compute square roots has
499a purpose that is entirely well-defined independent of the
500application. Therefore, Subsection 2d requires that any
501application-supplied function or table used by this function must
502be optional: if the application does not supply it, the square
503root function must still compute square roots.)\n\n
504
505These requirements apply to the modified work as a whole. If
506identifiable sections of that work are not derived from the Library,
507and can be reasonably considered independent and separate works in
508themselves, then this License, and its terms, do not apply to those
509sections when you distribute them as separate works. But when you
510distribute the same sections as part of a whole which is a work based
511on the Library, the distribution of the whole must be on the terms of
512this License, whose permissions for other licensees extend to the
513entire whole, and thus to each and every part regardless of who wrote
514it.\n\n
515
516Thus, it is not the intent of this section to claim rights or contest
517your rights to work written entirely by you; rather, the intent is to
518exercise the right to control the distribution of derivative or
519collective works based on the Library.\n\n
520
521In addition, mere aggregation of another work not based on the Library
522with the Library (or with a work based on the Library) on a volume of
523a storage or distribution medium does not bring the other work under
524the scope of this License.\n\n
525
526 3. You may opt to apply the terms of the ordinary GNU General Public
527License instead of this License to a given copy of the Library. To do
528this, you must alter all the notices that refer to this License, so
529that they refer to the ordinary GNU General Public License, version 2,
530instead of to this License. (If a newer version than version 2 of the
531ordinary GNU General Public License has appeared, then you can specify
532that version instead if you wish.) Do not make any other change in
533these notices.\n\n
534
535 Once this change is made in a given copy, it is irreversible for
536that copy, so the ordinary GNU General Public License applies to all
537subsequent copies and derivative works made from that copy.\n\n
538
539 This option is useful when you wish to copy part of the code of
540the Library into a program that is not a library.\n\n
541
542 4. You may copy and distribute the Library (or a portion or
543derivative of it, under Section 2) in object code or executable form
544under the terms of Sections 1 and 2 above provided that you accompany
545it with the complete corresponding machine-readable source code, which
546must be distributed under the terms of Sections 1 and 2 above on a
547medium customarily used for software interchange.\n\n
548
549 If distribution of object code is made by offering access to copy
550from a designated place, then offering equivalent access to copy the
551source code from the same place satisfies the requirement to
552distribute the source code, even though third parties are not
553compelled to copy the source along with the object code.\n\n
554
555 5. A program that contains no derivative of any portion of the
556Library, but is designed to work with the Library by being compiled or
557linked with it, is called a \"work that uses the Library\". Such a
558work, in isolation, is not a derivative work of the Library, and
559therefore falls outside the scope of this License.\n\n
560
561 However, linking a \"work that uses the Library\" with the Library
562creates an executable that is a derivative of the Library (because it
563contains portions of the Library), rather than a \"work that uses the
564library\". The executable is therefore covered by this License.
565Section 6 states terms for distribution of such executables.\n\n
566
567 When a \"work that uses the Library\" uses material from a header file
568that is part of the Library, the object code for the work may be a
569derivative work of the Library even though the source code is not.
570Whether this is true is especially significant if the work can be
571linked without the Library, or if the work is itself a library. The
572threshold for this to be true is not precisely defined by law.\n\n
573
574 If such an object file uses only numerical parameters, data
575structure layouts and accessors, and small macros and small inline
576functions (ten lines or less in length), then the use of the object
577file is unrestricted, regardless of whether it is legally a derivative
578work. (Executables containing this object code plus portions of the
579Library will still fall under Section 6.)\n\n
580
581 Otherwise, if the work is a derivative of the Library, you may
582distribute the object code for the work under the terms of Section 6.
583Any executables containing that work also fall under Section 6,
584whether or not they are linked directly with the Library itself.\n\n
585
586 6. As an exception to the Sections above, you may also combine or
587link a \"work that uses the Library\" with the Library to produce a
588work containing portions of the Library, and distribute that work
589under terms of your choice, provided that the terms permit
590modification of the work for the customer\'s own use and reverse
591engineering for debugging such modifications.\n\n
592
593 You must give prominent notice with each copy of the work that the
594Library is used in it and that the Library and its use are covered by
595this License. You must supply a copy of this License. If the work
596during execution displays copyright notices, you must include the
597copyright notice for the Library among them, as well as a reference
598directing the user to the copy of this License. Also, you must do one
599of these things:\n\n
600
601a) Accompany the work with the complete corresponding
602machine-readable source code for the Library including whatever
603changes were used in the work (which must be distributed under
604Sections 1 and 2 above); and, if the work is an executable linked
605with the Library, with the complete machine-readable \"work that
606uses the Library\", as object code and/or source code, so that the
607user can modify the Library and then relink to produce a modified
608executable containing the modified Library. (It is understood
609that the user who changes the contents of definitions files in the
610Library will not necessarily be able to recompile the application
611to use the modified definitions.)\n\n
612
613b) Use a suitable shared library mechanism for linking with the
614Library. A suitable mechanism is one that (1) uses at run time a
615copy of the library already present on the user\'s computer system,
616rather than copying library functions into the executable, and (2)
617will operate properly with a modified version of the library, if
618the user installs one, as long as the modified version is
619interface-compatible with the version that the work was made with.\n\n
620
621c) Accompany the work with a written offer, valid for at
622least three years, to give the same user the materials
623specified in Subsection 6a, above, for a charge no more
624than the cost of performing this distribution.\n\n
625
626d) If distribution of the work is made by offering access to copy
627from a designated place, offer equivalent access to copy the above
628specified materials from the same place.\n\n
629
630e) Verify that the user has already received a copy of these
631materials or that you have already sent this user a copy.\n\n
632
633 For an executable, the required form of the \"work that uses the
634Library\" must include any data and utility programs needed for
635reproducing the executable from it. However, as a special exception,
636the materials to be distributed need not include anything that is
637normally distributed (in either source or binary form) with the major
638components (compiler, kernel, and so on) of the operating system on
639which the executable runs, unless that component itself accompanies
640the executable.\n\n
641
642 It may happen that this requirement contradicts the license
643restrictions of other proprietary libraries that do not normally
644accompany the operating system. Such a contradiction means you cannot
645use both them and the Library together in an executable that you
646distribute.\n\n
647
648 7. You may place library facilities that are a work based on the
649Library side-by-side in a single library together with other library
650facilities not covered by this License, and distribute such a combined
651library, provided that the separate distribution of the work based on
652the Library and of the other library facilities is otherwise
653permitted, and provided that you do these two things:\n\n
654
655a) Accompany the combined library with a copy of the same work
656based on the Library, uncombined with any other library
657facilities. This must be distributed under the terms of the
658Sections above.\n\n
659
660b) Give prominent notice with the combined library of the fact
661that part of it is a work based on the Library, and explaining
662where to find the accompanying uncombined form of the same work.\n\n
663
664 8. You may not copy, modify, sublicense, link with, or distribute
665the Library except as expressly provided under this License. Any
666attempt otherwise to copy, modify, sublicense, link with, or
667distribute the Library is void, and will automatically terminate your
668rights under this License. However, parties who have received copies,
669or rights, from you under this License will not have their licenses
670terminated so long as such parties remain in full compliance.\n\n
671
672 9. You are not required to accept this License, since you have not
673signed it. However, nothing else grants you permission to modify or
674distribute the Library or its derivative works. These actions are
675prohibited by law if you do not accept this License. Therefore, by
676modifying or distributing the Library (or any work based on the
677Library), you indicate your acceptance of this License to do so, and
678all its terms and conditions for copying, distributing or modifying
679the Library or works based on it.\n\n
680
681 10. Each time you redistribute the Library (or any work based on the
682Library), the recipient automatically receives a license from the
683original licensor to copy, distribute, link with or modify the Library
684subject to these terms and conditions. You may not impose any further
685restrictions on the recipients\' exercise of the rights granted herein.
686You are not responsible for enforcing compliance by third parties with
687this License.\n\n
688
689 11. If, as a consequence of a court judgment or allegation of patent
690infringement or for any other reason (not limited to patent issues),
691conditions are imposed on you (whether by court order, agreement or
692otherwise) that contradict the conditions of this License, they do not
693excuse you from the conditions of this License. If you cannot
694distribute so as to satisfy simultaneously your obligations under this
695License and any other pertinent obligations, then as a consequence you
696may not distribute the Library at all. For example, if a patent
697license would not permit royalty-free redistribution of the Library by
698all those who receive copies directly or indirectly through you, then
699the only way you could satisfy both it and this License would be to
700refrain entirely from distribution of the Library.\n\n
701
702If any portion of this section is held invalid or unenforceable under any
703particular circumstance, the balance of the section is intended to apply,
704and the section as a whole is intended to apply in other circumstances.\n\n
705
706It is not the purpose of this section to induce you to infringe any
707patents or other property right claims or to contest validity of any
708such claims; this section has the sole purpose of protecting the
709integrity of the free software distribution system which is
710implemented by public license practices. Many people have made
711generous contributions to the wide range of software distributed
712through that system in reliance on consistent application of that
713system; it is up to the author/donor to decide if he or she is willing
714to distribute software through any other system and a licensee cannot
715impose that choice.\n\n
716
717This section is intended to make thoroughly clear what is believed to
718be a consequence of the rest of this License.\n\n
719
720 12. If the distribution and/or use of the Library is restricted in
721certain countries either by patents or by copyrighted interfaces, the
722original copyright holder who places the Library under this License may add
723an explicit geographical distribution limitation excluding those countries,
724so that distribution is permitted only in or among countries not thus
725excluded. In such case, this License incorporates the limitation as if
726written in the body of this License.\n\n
727
728 13. The Free Software Foundation may publish revised and/or new
729versions of the Lesser General Public License from time to time.
730Such new versions will be similar in spirit to the present version,
731but may differ in detail to address new problems or concerns.\n\n
732
733Each version is given a distinguishing version number. If the Library
734specifies a version number of this License which applies to it and
735"any later version\", you have the option of following the terms and
736conditions either of that version or of any later version published by
737the Free Software Foundation. If the Library does not specify a
738license version number, you may choose any version ever published by
739the Free Software Foundation.\n\n
740
741 14. If you wish to incorporate parts of the Library into other free
742programs whose distribution conditions are incompatible with these,
743write to the author to ask for permission. For software which is
744copyrighted by the Free Software Foundation, write to the Free
745Software Foundation; we sometimes make exceptions for this. Our
746decision will be guided by the two goals of preserving the free status
747of all derivatives of our free software and of promoting the sharing
748and reuse of software generally.\n\n
749
750NO WARRANTY\n\n
751
752 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
753WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
754EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
755OTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY
756KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
757IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
758PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
759LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
760THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n
761
762 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
763WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
764AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
765FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
766CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
767LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
768RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
769FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
770SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
771DAMAGES.
772 </string>
773 <string name="license_opus" translatable="false">Opus</string>
774 <string name="license_opus_description" translatable="false">Modern audio compression for the internet</string>
775 <string name="license_opus_link" translatable="false">https://github.com/xiph/opus</string>
776 <string name="license_opus_copyright" translatable="false">Copyright 2001–2011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, Erik de Castro Lopo</string>
777 <string name="license_opus_text" translatable="false">
778Redistribution and use in source and binary forms, with or without
779modification, are permitted provided that the following conditions
780are met:\n\n
781
782- Redistributions of source code must retain the above copyright
783notice, this list of conditions and the following disclaimer.\n\n
784
785- Redistributions in binary form must reproduce the above copyright
786notice, this list of conditions and the following disclaimer in the
787documentation and/or other materials provided with the distribution.\n\n
788
789- Neither the name of Internet Society, IETF or IETF Trust, nor the
790names of specific contributors, may be used to endorse or promote
791products derived from this software without specific prior written
792permission.\n\n
793
794THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
795``AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
796LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
797A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
798OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
799EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
800PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
801PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
802LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
803NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
804SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n
805
806Opus is subject to the royalty-free patent licenses which are
807specified at:\n\n
808
809Xiph.Org Foundation:
810https://datatracker.ietf.org/ipr/1524/ \n\n
811
812Microsoft Corporation:
813https://datatracker.ietf.org/ipr/1914/ \n\n
814
815Broadcom Corporation:
816https://datatracker.ietf.org/ipr/1526/
817 </string>
818 <string name="license_sirit" translatable="false">Sirit</string>
819 <string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string>
820 <string name="license_sirit_link" translatable="false">https://github.com/ReinUsesLisp/sirit</string>
821 <string name="license_sirit_copyright" translatable="false">Copyright © 2019, sirit All rights reserved.</string>
822 <string name="license_sirit_text" translatable="false">
823Redistribution and use in source and binary forms, with or without
824modification, are permitted provided that the following conditions are met:\n
825* Redistributions of source code must retain the above copyright
826 notice, this list of conditions and the following disclaimer.\n
827* Redistributions in binary form must reproduce the above copyright
828 notice, this list of conditions and the following disclaimer in the
829 documentation and/or other materials provided with the distribution.\n
830* Neither the name of the organization nor the
831 names of its contributors may be used to endorse or promote products
832 derived from this software without specific prior written permission.\n\n
833
834THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND
835ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
836WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
837DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
838DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
839(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
840LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
841ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
842(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
843SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
844 </string>
845 <string name="license_adreno_tools" translatable="false">Adreno Tools</string>
846 <string name="license_adreno_tools_description" translatable="false">A library for applying rootless Adreno GPU driver modifications/replacements</string>
847 <string name="license_adreno_tools_link" translatable="false">https://github.com/bylaws/libadrenotools</string>
848 <string name="license_adreno_tools_copyright" translatable="false">Copyright © 2021, Billy Laws</string>
849 <string name="license_adreno_tools_text" translatable="false">
850BSD 2-Clause License\n\n
851
852Redistribution and use in source and binary forms, with or without
853modification, are permitted provided that the following conditions are met:\n\n
854
8551. Redistributions of source code must retain the above copyright notice, this
856 list of conditions and the following disclaimer.\n\n
857
8582. Redistributions in binary form must reproduce the above copyright notice,
859 this list of conditions and the following disclaimer in the documentation
860 and/or other materials provided with the distribution.\n\n
861
862THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
863AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
864IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
865DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
866FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
867DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
868SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
869CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
870OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
871OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
872 </string>
873
874</resources>
diff --git a/src/android/app/src/main/res/values/styles.xml b/src/android/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..4f5de7360
--- /dev/null
+++ b/src/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,36 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <!-- Custom button styles -->
5 <style name="InGameMenuOption" parent="Widget.Material3.Button.TextButton">
6 <item name="android:layout_width">match_parent</item>
7 <item name="android:layout_height">48dp</item>
8 <item name="android:textColor">@android:color/black</item>
9 <item name="android:textSize">16sp</item>
10 <item name="android:fontFamily">sans-serif-condensed</item>
11 <item name="android:gravity">center_vertical|left</item>
12 <item name="android:paddingLeft">32dp</item>
13 <item name="android:paddingRight">32dp</item>
14 </style>
15
16 <style name="YuzuSlider" parent="Widget.Material3.Slider">
17 <item name="tickVisible">false</item>
18 <item name="labelBehavior">gone</item>
19 </style>
20
21 <style name="YuzuMaterialDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
22 <item name="colorPrimary">@color/yuzu_primaryContainer</item>
23 <item name="colorSurface">@color/yuzu_primaryContainer</item>
24 <item name="colorSecondary">@color/yuzu_primary</item>
25 <item name="android:textColorLink">@color/yuzu_primary</item>
26 <item name="buttonBarPositiveButtonStyle">@style/YuzuButton</item>
27 <item name="buttonBarNegativeButtonStyle">@style/YuzuButton</item>
28 <item name="buttonBarNeutralButtonStyle">@style/YuzuButton</item>
29 </style>
30
31 <style name="YuzuButton" parent="Widget.Material3.Button.TextButton.Dialog">
32 <item name="android:textColor">@color/yuzu_primary</item>
33 <item name="rippleColor">@color/yuzu_inversePrimary</item>
34 </style>
35
36</resources>
diff --git a/src/android/app/src/main/res/values/themes.xml b/src/android/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..60388b71e
--- /dev/null
+++ b/src/android/app/src/main/res/values/themes.xml
@@ -0,0 +1,51 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <style name="Theme.Yuzu.Splash.Main" parent="Theme.SplashScreen">
5 <item name="windowSplashScreenBackground">@color/yuzu_surface</item>
6 <item name="windowSplashScreenAnimatedIcon">@drawable/ic_yuzu</item>
7 <item name="postSplashScreenTheme">@style/Theme.Yuzu.Main</item>
8 </style>
9
10 <style name="Theme.Yuzu.Main" parent="Theme.Material3.DayNight.NoActionBar">
11 <item name="colorPrimary">@color/yuzu_primary</item>
12 <item name="colorOnPrimary">@color/yuzu_onPrimary</item>
13 <item name="colorPrimaryContainer">@color/yuzu_primaryContainer</item>
14 <item name="colorOnPrimaryContainer">@color/yuzu_onPrimaryContainer</item>
15 <item name="colorSecondary">@color/yuzu_secondary</item>
16 <item name="colorOnSecondary">@color/yuzu_onSecondary</item>
17 <item name="colorSecondaryContainer">@color/yuzu_secondaryContainer</item>
18 <item name="colorOnSecondaryContainer">@color/yuzu_onSecondaryContainer</item>
19 <item name="colorTertiary">@color/yuzu_tertiary</item>
20 <item name="colorOnTertiary">@color/yuzu_onTertiary</item>
21 <item name="colorTertiaryContainer">@color/yuzu_tertiaryContainer</item>
22 <item name="colorOnTertiaryContainer">@color/yuzu_onTertiaryContainer</item>
23 <item name="colorError">@color/yuzu_error</item>
24 <item name="colorErrorContainer">@color/yuzu_errorContainer</item>
25 <item name="colorOnError">@color/yuzu_onError</item>
26 <item name="colorOnErrorContainer">@color/yuzu_onErrorContainer</item>
27 <item name="android:colorBackground">@color/yuzu_background</item>
28 <item name="colorOnBackground">@color/yuzu_onBackground</item>
29 <item name="colorSurface">@color/yuzu_surface</item>
30 <item name="colorOnSurface">@color/yuzu_onSurface</item>
31 <item name="colorSurfaceVariant">@color/yuzu_surfaceVariant</item>
32 <item name="colorOnSurfaceVariant">@color/yuzu_onSurfaceVariant</item>
33 <item name="colorOutline">@color/yuzu_outline</item>
34 <item name="colorOnSurfaceInverse">@color/yuzu_inverseOnSurface</item>
35 <item name="colorSurfaceInverse">@color/yuzu_inverseSurface</item>
36 <item name="colorPrimaryInverse">@color/yuzu_inversePrimary</item>
37 <item name="android:shadowColor">@color/yuzu_shadow</item>
38
39 <item name="android:statusBarColor">@android:color/transparent</item>
40 <item name="android:navigationBarColor">@android:color/transparent</item>
41
42 <item name="sliderStyle">@style/YuzuSlider</item>
43 <item name="materialAlertDialogTheme">@style/YuzuMaterialDialog</item>
44
45 <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
46
47 <item name="android:enforceStatusBarContrast">false</item>
48 <item name="android:enforceNavigationBarContrast">false</item>
49 </style>
50
51</resources>
diff --git a/src/android/app/src/main/res/values/yuzu_colors.xml b/src/android/app/src/main/res/values/yuzu_colors.xml
new file mode 100644
index 000000000..5b7d189dc
--- /dev/null
+++ b/src/android/app/src/main/res/values/yuzu_colors.xml
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3
4 <color name="yuzu_primary">#990E00</color>
5 <color name="yuzu_onPrimary">#FFFFFF</color>
6 <color name="yuzu_primaryContainer">#EEDEDD</color>
7 <color name="yuzu_onPrimaryContainer">#400200</color>
8 <color name="yuzu_secondary">#775650</color>
9 <color name="yuzu_onSecondary">#FFFFFF</color>
10 <color name="yuzu_secondaryContainer">#FFDAD4</color>
11 <color name="yuzu_onSecondaryContainer">#2C1511</color>
12 <color name="yuzu_tertiary">#6F5C2E</color>
13 <color name="yuzu_onTertiary">#FFFFFF</color>
14 <color name="yuzu_tertiaryContainer">#FAE0A6</color>
15 <color name="yuzu_onTertiaryContainer">#251A00</color>
16 <color name="yuzu_error">#BA1A1A</color>
17 <color name="yuzu_errorContainer">#FFDAD6</color>
18 <color name="yuzu_onError">#FFFFFF</color>
19 <color name="yuzu_onErrorContainer">#410002</color>
20 <color name="yuzu_background">#FFFBFF</color>
21 <color name="yuzu_onBackground">#201A19</color>
22 <color name="yuzu_surface">#FFFBFF</color>
23 <color name="yuzu_onSurface">#201A19</color>
24 <color name="yuzu_surfaceVariant">#F5DDD9</color>
25 <color name="yuzu_onSurfaceVariant">#534340</color>
26 <color name="yuzu_outline">#857370</color>
27 <color name="yuzu_inverseOnSurface">#FBEEEB</color>
28 <color name="yuzu_inverseSurface">#362F2D</color>
29 <color name="yuzu_inversePrimary">#FFB4A6</color>
30 <color name="yuzu_shadow">#000000</color>
31 <color name="yuzu_surfaceTint">#B52612</color>
32 <color name="yuzu_outlineVariant">#D8C2BE</color>
33
34 <color name="yuzu_ea_background_start">#99FFE1</color>
35 <color name="yuzu_ea_background_end">#76C5FF</color>
36
37</resources>
diff --git a/src/android/app/src/main/res/xml/data_extraction_rules.xml b/src/android/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..c10efcf56
--- /dev/null
+++ b/src/android/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="utf-8"?>
2<full-backup-content>
3
4 <exclude
5 domain="external"
6 path="./load/" />
7
8 <exclude
9 domain="external"
10 path="./log/" />
11
12 <include
13 domain="external"
14 path="." />
15
16 <include
17 domain="sharedpref"
18 path="." />
19
20</full-backup-content>
diff --git a/src/android/app/src/main/res/xml/data_extraction_rules_api_31.xml b/src/android/app/src/main/res/xml/data_extraction_rules_api_31.xml
new file mode 100644
index 000000000..3ff6cc170
--- /dev/null
+++ b/src/android/app/src/main/res/xml/data_extraction_rules_api_31.xml
@@ -0,0 +1,43 @@
1<?xml version="1.0" encoding="utf-8"?>
2<data-extraction-rules>
3 <cloud-backup disableIfNoEncryptionCapabilities="false">
4
5 <exclude
6 domain="external"
7 path="./load/" />
8
9 <exclude
10 domain="external"
11 path="./log/" />
12
13 <include
14 domain="external"
15 path="." />
16
17 <include
18 domain="sharedpref"
19 path="." />
20
21 </cloud-backup>
22
23 <device-transfer>
24
25 <exclude
26 domain="external"
27 path="./load/" />
28
29 <exclude
30 domain="external"
31 path="./log/" />
32
33 <include
34 domain="external"
35 path="." />
36
37 <include
38 domain="sharedpref"
39 path="." />
40
41 </device-transfer>
42
43</data-extraction-rules>
diff --git a/src/android/app/src/main/res/xml/locales_config.xml b/src/android/app/src/main/res/xml/locales_config.xml
new file mode 100644
index 000000000..51b88d9dc
--- /dev/null
+++ b/src/android/app/src/main/res/xml/locales_config.xml
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="utf-8"?>
2<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
3 <locale android:name="en" /> <!-- English (default) -->
4 <locale android:name="de" /> <!-- German -->
5 <locale android:name="es" /> <!-- Spanish -->
6 <locale android:name="fr" /> <!-- French -->
7 <locale android:name="it" /> <!-- Italian -->
8 <locale android:name="ja" /> <!-- Japanese -->
9 <locale android:name="nb" /> <!-- Norwegian Bokmal -->
10 <locale android:name="pl" /> <!-- Polish -->
11 <locale android:name="pt-rBR" /> <!-- Portuguese (Brazil) -->
12 <locale android:name="pt-RPT" /> <!-- Portuguese (Portugal) -->
13 <locale android:name="ru" /> <!-- Russian -->
14 <locale android:name="uk" /> <!-- Ukranian -->
15 <locale android:name="zh-rCN" /> <!-- Chinese (China) -->
16 <locale android:name="zh-rTW" /> <!-- Chinese (Taiwan) -->
17</locale-config>
diff --git a/src/android/app/src/main/res/xml/nfc_tech_filter.xml b/src/android/app/src/main/res/xml/nfc_tech_filter.xml
new file mode 100644
index 000000000..eb4497446
--- /dev/null
+++ b/src/android/app/src/main/res/xml/nfc_tech_filter.xml
@@ -0,0 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
3 <tech-list>
4 <tech>android.nfc.tech.NfcA</tech>
5 </tech-list>
6</resources>
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
new file mode 100644
index 000000000..e19e8ce58
--- /dev/null
+++ b/src/android/build.gradle.kts
@@ -0,0 +1,13 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4// Top-level build file where you can add configuration options common to all sub-projects/modules.
5plugins {
6 id("com.android.application") version "8.0.2" apply false
7 id("com.android.library") version "8.0.2" apply false
8 id("org.jetbrains.kotlin.android") version "1.8.21" apply false
9}
10
11tasks.register("clean").configure {
12 delete(rootProject.buildDir)
13}
diff --git a/src/android/gradle.properties b/src/android/gradle.properties
new file mode 100644
index 000000000..e2f278f33
--- /dev/null
+++ b/src/android/gradle.properties
@@ -0,0 +1,17 @@
1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4# Project-wide Gradle settings.
5# IDE (e.g. Android Studio) users:
6# Gradle settings configured through the IDE *will override*
7# any settings specified in this file.
8# For more details on how to configure your build environment visit
9# http://www.gradle.org/docs/current/userguide/build_environment.html
10# Specifies the JVM arguments used for the daemon process.
11# The setting is particularly useful for tweaking memory settings.
12org.gradle.jvmargs=-Xms512m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
13android.useAndroidX=true
14# Kotlin code style for this project: "official" or "obsolete":
15kotlin.code.style=official
16kotlin.parallel.tasks.in.project=true
17android.defaults.buildfeatures.buildconfig=true
diff --git a/src/android/gradle/wrapper/gradle-wrapper.jar b/src/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..7a3265ee9
--- /dev/null
+++ b/src/android/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/src/android/gradle/wrapper/gradle-wrapper.properties b/src/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..578c71b94
--- /dev/null
+++ b/src/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
1#Sun Feb 21 18:16:59 EST 2021
2distributionBase=GRADLE_USER_HOME
3distributionPath=wrapper/dists
4zipStoreBase=GRADLE_USER_HOME
5zipStorePath=wrapper/dists
6distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
diff --git a/src/android/gradlew b/src/android/gradlew
new file mode 100755
index 000000000..afa127966
--- /dev/null
+++ b/src/android/gradlew
@@ -0,0 +1,175 @@
1#!/usr/bin/env sh
2
3# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
4# SPDX-License-Identifier: GPL-3.0-or-later
5
6##############################################################################
7##
8## Gradle start up script for UN*X
9##
10##############################################################################
11
12# Attempt to set APP_HOME
13# Resolve links: $0 may be a link
14PRG="$0"
15# Need this for relative symlinks.
16while [ -h "$PRG" ] ; do
17 ls=`ls -ld "$PRG"`
18 link=`expr "$ls" : '.*-> \(.*\)$'`
19 if expr "$link" : '/.*' > /dev/null; then
20 PRG="$link"
21 else
22 PRG=`dirname "$PRG"`"/$link"
23 fi
24done
25SAVED="`pwd`"
26cd "`dirname \"$PRG\"`/" >/dev/null
27APP_HOME="`pwd -P`"
28cd "$SAVED" >/dev/null
29
30APP_NAME="Gradle"
31APP_BASE_NAME=`basename "$0"`
32
33# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
34DEFAULT_JVM_OPTS=""
35
36# Use the maximum available, or set MAX_FD != -1 to use that value.
37MAX_FD="maximum"
38
39warn () {
40 echo "$*"
41}
42
43die () {
44 echo
45 echo "$*"
46 echo
47 exit 1
48}
49
50# OS specific support (must be 'true' or 'false').
51cygwin=false
52msys=false
53darwin=false
54nonstop=false
55case "`uname`" in
56 CYGWIN* )
57 cygwin=true
58 ;;
59 Darwin* )
60 darwin=true
61 ;;
62 MINGW* )
63 msys=true
64 ;;
65 NONSTOP* )
66 nonstop=true
67 ;;
68esac
69
70CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
71
72# Determine the Java command to use to start the JVM.
73if [ -n "$JAVA_HOME" ] ; then
74 if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
75 # IBM's JDK on AIX uses strange locations for the executables
76 JAVACMD="$JAVA_HOME/jre/sh/java"
77 else
78 JAVACMD="$JAVA_HOME/bin/java"
79 fi
80 if [ ! -x "$JAVACMD" ] ; then
81 die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
82
83Please set the JAVA_HOME variable in your environment to match the
84location of your Java installation."
85 fi
86else
87 JAVACMD="java"
88 which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
89
90Please set the JAVA_HOME variable in your environment to match the
91location of your Java installation."
92fi
93
94# Increase the maximum file descriptors if we can.
95if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
96 MAX_FD_LIMIT=`ulimit -H -n`
97 if [ $? -eq 0 ] ; then
98 if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
99 MAX_FD="$MAX_FD_LIMIT"
100 fi
101 ulimit -n $MAX_FD
102 if [ $? -ne 0 ] ; then
103 warn "Could not set maximum file descriptor limit: $MAX_FD"
104 fi
105 else
106 warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
107 fi
108fi
109
110# For Darwin, add options to specify how the application appears in the dock
111if $darwin; then
112 GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
113fi
114
115# For Cygwin, switch paths to Windows format before running java
116if $cygwin ; then
117 APP_HOME=`cygpath --path --mixed "$APP_HOME"`
118 CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
119 JAVACMD=`cygpath --unix "$JAVACMD"`
120
121 # We build the pattern for arguments to be converted via cygpath
122 ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
123 SEP=""
124 for dir in $ROOTDIRSRAW ; do
125 ROOTDIRS="$ROOTDIRS$SEP$dir"
126 SEP="|"
127 done
128 OURCYGPATTERN="(^($ROOTDIRS))"
129 # Add a user-defined pattern to the cygpath arguments
130 if [ "$GRADLE_CYGPATTERN" != "" ] ; then
131 OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
132 fi
133 # Now convert the arguments - kludge to limit ourselves to /bin/sh
134 i=0
135 for arg in "$@" ; do
136 CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
137 CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
138
139 if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
140 eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
141 else
142 eval `echo args$i`="\"$arg\""
143 fi
144 i=$((i+1))
145 done
146 case $i in
147 (0) set -- ;;
148 (1) set -- "$args0" ;;
149 (2) set -- "$args0" "$args1" ;;
150 (3) set -- "$args0" "$args1" "$args2" ;;
151 (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
152 (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
153 (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
154 (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
155 (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
156 (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
157 esac
158fi
159
160# Escape application args
161save () {
162 for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
163 echo " "
164}
165APP_ARGS=$(save "$@")
166
167# Collect all arguments for the java command, following the shell quoting and substitution rules
168eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
169
170# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
171if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
172 cd "$(dirname "$0")"
173fi
174
175exec "$JAVACMD" "$@"
diff --git a/src/android/gradlew.bat b/src/android/gradlew.bat
new file mode 100644
index 000000000..be152d108
--- /dev/null
+++ b/src/android/gradlew.bat
@@ -0,0 +1,87 @@
1@rem SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2@rem SPDX-License-Identifier: GPL-3.0-or-later
3
4@if "%DEBUG%" == "" @echo off
5@rem ##########################################################################
6@rem
7@rem Gradle startup script for Windows
8@rem
9@rem ##########################################################################
10
11@rem Set local scope for the variables with windows NT shell
12if "%OS%"=="Windows_NT" setlocal
13
14set DIRNAME=%~dp0
15if "%DIRNAME%" == "" set DIRNAME=.
16set APP_BASE_NAME=%~n0
17set APP_HOME=%DIRNAME%
18
19@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
20set DEFAULT_JVM_OPTS=
21
22@rem Find java.exe
23if defined JAVA_HOME goto findJavaFromJavaHome
24
25set JAVA_EXE=java.exe
26%JAVA_EXE% -version >NUL 2>&1
27if "%ERRORLEVEL%" == "0" goto init
28
29echo.
30echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
31echo.
32echo Please set the JAVA_HOME variable in your environment to match the
33echo location of your Java installation.
34
35goto fail
36
37:findJavaFromJavaHome
38set JAVA_HOME=%JAVA_HOME:"=%
39set JAVA_EXE=%JAVA_HOME%/bin/java.exe
40
41if exist "%JAVA_EXE%" goto init
42
43echo.
44echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
45echo.
46echo Please set the JAVA_HOME variable in your environment to match the
47echo location of your Java installation.
48
49goto fail
50
51:init
52@rem Get command-line arguments, handling Windows variants
53
54if not "%OS%" == "Windows_NT" goto win9xME_args
55
56:win9xME_args
57@rem Slurp the command line arguments.
58set CMD_LINE_ARGS=
59set _SKIP=2
60
61:win9xME_args_slurp
62if "x%~1" == "x" goto execute
63
64set CMD_LINE_ARGS=%*
65
66:execute
67@rem Setup the command line
68
69set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
70
71@rem Execute Gradle
72"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
73
74:end
75@rem End local scope for the variables with windows NT shell
76if "%ERRORLEVEL%"=="0" goto mainEnd
77
78:fail
79rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
80rem the _cmd.exe /c_ return code!
81if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
82exit /b 1
83
84:mainEnd
85if "%OS%"=="Windows_NT" endlocal
86
87:omega
diff --git a/src/android/settings.gradle.kts b/src/android/settings.gradle.kts
new file mode 100644
index 000000000..af910b906
--- /dev/null
+++ b/src/android/settings.gradle.kts
@@ -0,0 +1,21 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4pluginManagement {
5 repositories {
6 gradlePluginPortal()
7 google()
8 mavenCentral()
9 }
10}
11
12@Suppress("UnstableApiUsage")
13dependencyResolutionManagement {
14 repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
15 repositories {
16 google()
17 mavenCentral()
18 }
19}
20
21include(":app")
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 07a679c32..703ef4494 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -47,12 +47,4 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
47 return *adsp; 47 return *adsp;
48} 48}
49 49
50void AudioCore::SetNVDECActive(bool active) {
51 nvdec_active = active;
52}
53
54bool AudioCore::IsNVDECActive() const {
55 return nvdec_active;
56}
57
58} // namespace AudioCore 50} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index e33e00a3e..ea047773e 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -57,18 +57,6 @@ public:
57 */ 57 */
58 AudioRenderer::ADSP::ADSP& GetADSP(); 58 AudioRenderer::ADSP::ADSP& GetADSP();
59 59
60 /**
61 * Toggle NVDEC state, used to avoid stall in playback.
62 *
63 * @param active - Set true if nvdec is active, otherwise false.
64 */
65 void SetNVDECActive(bool active);
66
67 /**
68 * Get NVDEC state.
69 */
70 bool IsNVDECActive() const;
71
72private: 60private:
73 /** 61 /**
74 * Create the sinks on startup. 62 * Create the sinks on startup.
@@ -83,8 +71,6 @@ private:
83 std::unique_ptr<Sink::Sink> input_sink; 71 std::unique_ptr<Sink::Sink> input_sink;
84 /// The ADSP in the sysmodule 72 /// The ADSP in the sysmodule
85 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; 73 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
86 /// Is NVDec currently active?
87 bool nvdec_active{false};
88}; 74};
89 75
90} // namespace AudioCore 76} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
index 1cbeed302..8bc39f9f9 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.cpp
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -105,7 +105,7 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
105 } 105 }
106 106
107 mailbox = mailbox_; 107 mailbox = mailbox_;
108 thread = std::thread(&AudioRenderer::ThreadFunc, this); 108 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); });
109 running = true; 109 running = true;
110} 110}
111 111
@@ -131,7 +131,7 @@ void AudioRenderer::CreateSinkStreams() {
131 } 131 }
132} 132}
133 133
134void AudioRenderer::ThreadFunc() { 134void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
135 static constexpr char name[]{"AudioRenderer"}; 135 static constexpr char name[]{"AudioRenderer"};
136 MicroProfileOnThreadCreate(name); 136 MicroProfileOnThreadCreate(name);
137 Common::SetCurrentThreadName(name); 137 Common::SetCurrentThreadName(name);
@@ -146,7 +146,7 @@ void AudioRenderer::ThreadFunc() {
146 146
147 constexpr u64 max_process_time{2'304'000ULL}; 147 constexpr u64 max_process_time{2'304'000ULL};
148 148
149 while (true) { 149 while (!stop_token.stop_requested()) {
150 auto message{mailbox->ADSPWaitMessage()}; 150 auto message{mailbox->ADSPWaitMessage()};
151 switch (message) { 151 switch (message) {
152 case RenderMessage::AudioRenderer_Shutdown: 152 case RenderMessage::AudioRenderer_Shutdown:
@@ -194,7 +194,7 @@ void AudioRenderer::ThreadFunc() {
194 max_time = std::min(command_buffer.time_limit, max_time); 194 max_time = std::min(command_buffer.time_limit, max_time);
195 command_list_processor.SetProcessTimeMax(max_time); 195 command_list_processor.SetProcessTimeMax(max_time);
196 196
197 streams[index]->WaitFreeSpace(); 197 streams[index]->WaitFreeSpace(stop_token);
198 198
199 // Process the command list 199 // Process the command list
200 { 200 {
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
index 85ce6a269..88e558183 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.h
+++ b/src/audio_core/renderer/adsp/audio_renderer.h
@@ -177,7 +177,7 @@ private:
177 /** 177 /**
178 * Main AudioRenderer thread, responsible for processing the command lists. 178 * Main AudioRenderer thread, responsible for processing the command lists.
179 */ 179 */
180 void ThreadFunc(); 180 void ThreadFunc(std::stop_token stop_token);
181 181
182 /** 182 /**
183 * Creates the streams which will receive the processed samples. 183 * Creates the streams which will receive the processed samples.
@@ -187,7 +187,7 @@ private:
187 /// Core system 187 /// Core system
188 Core::System& system; 188 Core::System& system;
189 /// Main thread 189 /// Main thread
190 std::thread thread{}; 190 std::jthread thread{};
191 /// The current state 191 /// The current state
192 std::atomic<bool> running{}; 192 std::atomic<bool> running{};
193 /// The active mailbox 193 /// The active mailbox
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 2331aaff9..f44fedfd5 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -269,12 +269,13 @@ u64 SinkStream::GetExpectedPlayedSampleCount() {
269 return std::min<u64>(exp_played_sample_count, max_played_sample_count) + TargetSampleCount * 3; 269 return std::min<u64>(exp_played_sample_count, max_played_sample_count) + TargetSampleCount * 3;
270} 270}
271 271
272void SinkStream::WaitFreeSpace() { 272void SinkStream::WaitFreeSpace(std::stop_token stop_token) {
273 std::unique_lock lk{release_mutex}; 273 std::unique_lock lk{release_mutex};
274 release_cv.wait_for(lk, std::chrono::milliseconds(5), 274 release_cv.wait_for(lk, std::chrono::milliseconds(5),
275 [this]() { return queued_buffers < max_queue_size; }); 275 [this]() { return queued_buffers < max_queue_size; });
276 if (queued_buffers > max_queue_size + 3) { 276 if (queued_buffers > max_queue_size + 3) {
277 release_cv.wait(lk, [this]() { return queued_buffers < max_queue_size; }); 277 Common::CondvarWait(release_cv, lk, stop_token,
278 [this] { return queued_buffers < max_queue_size; });
278 } 279 }
279} 280}
280 281
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h
index 21b5b40a1..41cbadc9c 100644
--- a/src/audio_core/sink/sink_stream.h
+++ b/src/audio_core/sink/sink_stream.h
@@ -13,6 +13,7 @@
13 13
14#include "audio_core/common/common.h" 14#include "audio_core/common/common.h"
15#include "common/common_types.h" 15#include "common/common_types.h"
16#include "common/polyfill_thread.h"
16#include "common/reader_writer_queue.h" 17#include "common/reader_writer_queue.h"
17#include "common/ring_buffer.h" 18#include "common/ring_buffer.h"
18#include "common/thread.h" 19#include "common/thread.h"
@@ -210,7 +211,7 @@ public:
210 /** 211 /**
211 * Waits for free space in the sample ring buffer 212 * Waits for free space in the sample ring buffer
212 */ 213 */
213 void WaitFreeSpace(); 214 void WaitFreeSpace(std::stop_token stop_token);
214 215
215protected: 216protected:
216 /// Core system 217 /// Core system
@@ -252,7 +253,7 @@ private:
252 /// Set via IAudioDevice service calls 253 /// Set via IAudioDevice service calls
253 f32 device_volume{1.0f}; 254 f32 device_volume{1.0f};
254 /// Signalled when ring buffer entries are consumed 255 /// Signalled when ring buffer entries are consumed
255 std::condition_variable release_cv; 256 std::condition_variable_any release_cv;
256 std::mutex release_mutex; 257 std::mutex release_mutex;
257}; 258};
258 259
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 13ed68b3f..efc4a9fe9 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -155,6 +155,14 @@ if (WIN32)
155 target_link_libraries(common PRIVATE ntdll) 155 target_link_libraries(common PRIVATE ntdll)
156endif() 156endif()
157 157
158if(ANDROID)
159 target_sources(common
160 PRIVATE
161 fs/fs_android.cpp
162 fs/fs_android.h
163 )
164endif()
165
158if(ARCHITECTURE_x86_64) 166if(ARCHITECTURE_x86_64)
159 target_sources(common 167 target_sources(common
160 PRIVATE 168 PRIVATE
@@ -194,6 +202,11 @@ create_target_directory_groups(common)
194target_link_libraries(common PUBLIC Boost::context Boost::headers fmt::fmt microprofile Threads::Threads) 202target_link_libraries(common PUBLIC Boost::context Boost::headers fmt::fmt microprofile Threads::Threads)
195target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle) 203target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle)
196 204
205if (ANDROID)
206 # For ASharedMemory_create
207 target_link_libraries(common PRIVATE android)
208endif()
209
197if (YUZU_USE_PRECOMPILED_HEADERS) 210if (YUZU_USE_PRECOMPILED_HEADERS)
198 target_precompile_headers(common PRIVATE precompiled_headers.h) 211 target_precompile_headers(common PRIVATE precompiled_headers.h)
199endif() 212endif()
diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp
index 054277a2b..4fabe7e52 100644
--- a/src/common/dynamic_library.cpp
+++ b/src/common/dynamic_library.cpp
@@ -22,6 +22,8 @@ DynamicLibrary::DynamicLibrary(const char* filename) {
22 void(Open(filename)); 22 void(Open(filename));
23} 23}
24 24
25DynamicLibrary::DynamicLibrary(void* handle_) : handle{handle_} {}
26
25DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept 27DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept
26 : handle{std::exchange(rhs.handle, nullptr)} {} 28 : handle{std::exchange(rhs.handle, nullptr)} {}
27 29
diff --git a/src/common/dynamic_library.h b/src/common/dynamic_library.h
index f42bdf441..662d454d4 100644
--- a/src/common/dynamic_library.h
+++ b/src/common/dynamic_library.h
@@ -20,6 +20,9 @@ public:
20 /// Automatically loads the specified library. Call IsOpen() to check validity before use. 20 /// Automatically loads the specified library. Call IsOpen() to check validity before use.
21 explicit DynamicLibrary(const char* filename); 21 explicit DynamicLibrary(const char* filename);
22 22
23 /// Initializes the dynamic library with an already opened handle.
24 explicit DynamicLibrary(void* handle_);
25
23 /// Moves the library. 26 /// Moves the library.
24 DynamicLibrary(DynamicLibrary&&) noexcept; 27 DynamicLibrary(DynamicLibrary&&) noexcept;
25 DynamicLibrary& operator=(DynamicLibrary&&) noexcept; 28 DynamicLibrary& operator=(DynamicLibrary&&) noexcept;
diff --git a/src/common/error.cpp b/src/common/error.cpp
index ddb03bd45..1b2009db7 100644
--- a/src/common/error.cpp
+++ b/src/common/error.cpp
@@ -30,7 +30,8 @@ std::string NativeErrorToString(int e) {
30 return ret; 30 return ret;
31#else 31#else
32 char err_str[255]; 32 char err_str[255];
33#if defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) 33#if defined(ANDROID) || \
34 (defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)))
34 // Thread safe (GNU-specific) 35 // Thread safe (GNU-specific)
35 const char* str = strerror_r(e, err_str, sizeof(err_str)); 36 const char* str = strerror_r(e, err_str, sizeof(err_str));
36 return std::string(str); 37 return std::string(str);
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp
index 656b03cc5..b0b25eb43 100644
--- a/src/common/fs/file.cpp
+++ b/src/common/fs/file.cpp
@@ -5,6 +5,9 @@
5 5
6#include "common/fs/file.h" 6#include "common/fs/file.h"
7#include "common/fs/fs.h" 7#include "common/fs/fs.h"
8#ifdef ANDROID
9#include "common/fs/fs_android.h"
10#endif
8#include "common/logging/log.h" 11#include "common/logging/log.h"
9 12
10#ifdef _WIN32 13#ifdef _WIN32
@@ -252,6 +255,23 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File
252 } else { 255 } else {
253 _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); 256 _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
254 } 257 }
258#elif ANDROID
259 if (Android::IsContentUri(path)) {
260 ASSERT_MSG(mode == FileAccessMode::Read, "Content URI file access is for read-only!");
261 const auto fd = Android::OpenContentUri(path, Android::OpenMode::Read);
262 if (fd != -1) {
263 file = fdopen(fd, "r");
264 const auto error_num = errno;
265 if (error_num != 0 && file == nullptr) {
266 LOG_ERROR(Common_Filesystem, "Error opening file: {}, error: {}", path.c_str(),
267 strerror(error_num));
268 }
269 } else {
270 LOG_ERROR(Common_Filesystem, "Error opening file: {}", path.c_str());
271 }
272 } else {
273 file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
274 }
255#else 275#else
256 file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); 276 file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
257#endif 277#endif
@@ -372,6 +392,23 @@ u64 IOFile::GetSize() const {
372 // Flush any unwritten buffered data into the file prior to retrieving the file size. 392 // Flush any unwritten buffered data into the file prior to retrieving the file size.
373 std::fflush(file); 393 std::fflush(file);
374 394
395#if ANDROID
396 u64 file_size = 0;
397 if (Android::IsContentUri(file_path)) {
398 file_size = Android::GetSize(file_path);
399 } else {
400 std::error_code ec;
401
402 file_size = fs::file_size(file_path, ec);
403
404 if (ec) {
405 LOG_ERROR(Common_Filesystem,
406 "Failed to retrieve the file size of path={}, ec_message={}",
407 PathToUTF8String(file_path), ec.message());
408 return 0;
409 }
410 }
411#else
375 std::error_code ec; 412 std::error_code ec;
376 413
377 const auto file_size = fs::file_size(file_path, ec); 414 const auto file_size = fs::file_size(file_path, ec);
@@ -381,6 +418,7 @@ u64 IOFile::GetSize() const {
381 PathToUTF8String(file_path), ec.message()); 418 PathToUTF8String(file_path), ec.message());
382 return 0; 419 return 0;
383 } 420 }
421#endif
384 422
385 return file_size; 423 return file_size;
386} 424}
diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp
new file mode 100644
index 000000000..298a79bac
--- /dev/null
+++ b/src/common/fs/fs_android.cpp
@@ -0,0 +1,98 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/fs/fs_android.h"
5
6namespace Common::FS::Android {
7
8JNIEnv* GetEnvForThread() {
9 thread_local static struct OwnedEnv {
10 OwnedEnv() {
11 status = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
12 if (status == JNI_EDETACHED)
13 g_jvm->AttachCurrentThread(&env, nullptr);
14 }
15
16 ~OwnedEnv() {
17 if (status == JNI_EDETACHED)
18 g_jvm->DetachCurrentThread();
19 }
20
21 int status;
22 JNIEnv* env = nullptr;
23 } owned;
24 return owned.env;
25}
26
27void RegisterCallbacks(JNIEnv* env, jclass clazz) {
28 env->GetJavaVM(&g_jvm);
29 native_library = clazz;
30
31#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
32 F(JMethodID, JMethodName, Signature)
33#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
34 F(JMethodID, JMethodName, Signature)
35#define F(JMethodID, JMethodName, Signature) \
36 JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature);
37 ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
38 ANDROID_STORAGE_FUNCTIONS(FS)
39#undef F
40#undef FS
41#undef FR
42}
43
44void UnRegisterCallbacks() {
45#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
46#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
47#define F(JMethodID) JMethodID = nullptr;
48 ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
49 ANDROID_STORAGE_FUNCTIONS(FS)
50#undef F
51#undef FS
52#undef FR
53}
54
55bool IsContentUri(const std::string& path) {
56 constexpr std::string_view prefix = "content://";
57 if (path.size() < prefix.size()) [[unlikely]] {
58 return false;
59 }
60
61 return path.find(prefix) == 0;
62}
63
64int OpenContentUri(const std::string& filepath, OpenMode openmode) {
65 if (open_content_uri == nullptr)
66 return -1;
67
68 const char* mode = "";
69 switch (openmode) {
70 case OpenMode::Read:
71 mode = "r";
72 break;
73 default:
74 UNIMPLEMENTED();
75 return -1;
76 }
77 auto env = GetEnvForThread();
78 jstring j_filepath = env->NewStringUTF(filepath.c_str());
79 jstring j_mode = env->NewStringUTF(mode);
80 return env->CallStaticIntMethod(native_library, open_content_uri, j_filepath, j_mode);
81}
82
83#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
84 F(FunctionName, ReturnValue, JMethodID, Caller)
85#define F(FunctionName, ReturnValue, JMethodID, Caller) \
86 ReturnValue FunctionName(const std::string& filepath) { \
87 if (JMethodID == nullptr) { \
88 return 0; \
89 } \
90 auto env = GetEnvForThread(); \
91 jstring j_filepath = env->NewStringUTF(filepath.c_str()); \
92 return env->Caller(native_library, JMethodID, j_filepath); \
93 }
94ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
95#undef F
96#undef FR
97
98} // namespace Common::FS::Android
diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h
new file mode 100644
index 000000000..bb8a52648
--- /dev/null
+++ b/src/common/fs/fs_android.h
@@ -0,0 +1,62 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include <vector>
8#include <jni.h>
9
10#define ANDROID_STORAGE_FUNCTIONS(V) \
11 V(OpenContentUri, int, (const std::string& filepath, OpenMode openmode), open_content_uri, \
12 "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I")
13
14#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \
15 V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J")
16
17namespace Common::FS::Android {
18
19static JavaVM* g_jvm = nullptr;
20static jclass native_library = nullptr;
21
22#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
23#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
24#define F(JMethodID) static jmethodID JMethodID = nullptr;
25ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
26ANDROID_STORAGE_FUNCTIONS(FS)
27#undef F
28#undef FS
29#undef FR
30
31enum class OpenMode {
32 Read,
33 Write,
34 ReadWrite,
35 WriteAppend,
36 WriteTruncate,
37 ReadWriteAppend,
38 ReadWriteTruncate,
39 Never
40};
41
42void RegisterCallbacks(JNIEnv* env, jclass clazz);
43
44void UnRegisterCallbacks();
45
46bool IsContentUri(const std::string& path);
47
48#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
49 F(FunctionName, Parameters, ReturnValue)
50#define F(FunctionName, Parameters, ReturnValue) ReturnValue FunctionName Parameters;
51ANDROID_STORAGE_FUNCTIONS(FS)
52#undef F
53#undef FS
54
55#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
56 F(FunctionName, ReturnValue)
57#define F(FunctionName, ReturnValue) ReturnValue FunctionName(const std::string& filepath);
58ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
59#undef F
60#undef FR
61
62} // namespace Common::FS::Android
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index c77c112f1..61bac9eba 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -10,6 +10,7 @@
10 10
11// Sub-directories contained within a yuzu data directory 11// Sub-directories contained within a yuzu data directory
12 12
13#define AMIIBO_DIR "amiibo"
13#define CACHE_DIR "cache" 14#define CACHE_DIR "cache"
14#define CONFIG_DIR "config" 15#define CONFIG_DIR "config"
15#define DUMP_DIR "dump" 16#define DUMP_DIR "dump"
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index defa3e918..d71cfacc6 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -6,6 +6,9 @@
6#include <unordered_map> 6#include <unordered_map>
7 7
8#include "common/fs/fs.h" 8#include "common/fs/fs.h"
9#ifdef ANDROID
10#include "common/fs/fs_android.h"
11#endif
9#include "common/fs/fs_paths.h" 12#include "common/fs/fs_paths.h"
10#include "common/fs/path_util.h" 13#include "common/fs/path_util.h"
11#include "common/logging/log.h" 14#include "common/logging/log.h"
@@ -80,9 +83,7 @@ public:
80 yuzu_paths.insert_or_assign(yuzu_path, new_path); 83 yuzu_paths.insert_or_assign(yuzu_path, new_path);
81 } 84 }
82 85
83private: 86 void Reinitialize(fs::path yuzu_path = {}) {
84 PathManagerImpl() {
85 fs::path yuzu_path;
86 fs::path yuzu_path_cache; 87 fs::path yuzu_path_cache;
87 fs::path yuzu_path_config; 88 fs::path yuzu_path_config;
88 89
@@ -95,6 +96,10 @@ private:
95 96
96 yuzu_path_cache = yuzu_path / CACHE_DIR; 97 yuzu_path_cache = yuzu_path / CACHE_DIR;
97 yuzu_path_config = yuzu_path / CONFIG_DIR; 98 yuzu_path_config = yuzu_path / CONFIG_DIR;
99#elif ANDROID
100 ASSERT(!yuzu_path.empty());
101 yuzu_path_cache = yuzu_path / CACHE_DIR;
102 yuzu_path_config = yuzu_path / CONFIG_DIR;
98#else 103#else
99 yuzu_path = GetCurrentDir() / PORTABLE_DIR; 104 yuzu_path = GetCurrentDir() / PORTABLE_DIR;
100 105
@@ -109,6 +114,7 @@ private:
109#endif 114#endif
110 115
111 GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); 116 GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
117 GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
112 GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache); 118 GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
113 GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config); 119 GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
114 GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR); 120 GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
@@ -122,6 +128,11 @@ private:
122 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); 128 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
123 } 129 }
124 130
131private:
132 PathManagerImpl() {
133 Reinitialize();
134 }
135
125 ~PathManagerImpl() = default; 136 ~PathManagerImpl() = default;
126 137
127 void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { 138 void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
@@ -210,6 +221,10 @@ fs::path RemoveTrailingSeparators(const fs::path& path) {
210 return fs::path{string_path}; 221 return fs::path{string_path};
211} 222}
212 223
224void SetAppDirectory(const std::string& app_directory) {
225 PathManagerImpl::GetInstance().Reinitialize(app_directory);
226}
227
213const fs::path& GetYuzuPath(YuzuPath yuzu_path) { 228const fs::path& GetYuzuPath(YuzuPath yuzu_path) {
214 return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path); 229 return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path);
215} 230}
@@ -350,6 +365,12 @@ std::vector<std::string> SplitPathComponents(std::string_view filename) {
350 365
351std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { 366std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
352 std::string path(path_); 367 std::string path(path_);
368#ifdef ANDROID
369 if (Android::IsContentUri(path)) {
370 return path;
371 }
372#endif // ANDROID
373
353 char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; 374 char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
354 char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; 375 char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
355 376
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 13d713f1e..ba28964d0 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -12,6 +12,7 @@ namespace Common::FS {
12 12
13enum class YuzuPath { 13enum class YuzuPath {
14 YuzuDir, // Where yuzu stores its data. 14 YuzuDir, // Where yuzu stores its data.
15 AmiiboDir, // Where Amiibo backups are stored.
15 CacheDir, // Where cached filesystem data is stored. 16 CacheDir, // Where cached filesystem data is stored.
16 ConfigDir, // Where config files are stored. 17 ConfigDir, // Where config files are stored.
17 DumpDir, // Where dumped data is stored. 18 DumpDir, // Where dumped data is stored.
@@ -181,6 +182,14 @@ template <typename Path>
181#endif 182#endif
182 183
183/** 184/**
185 * Sets the directory used for application storage. Used on Android where we do not know internal
186 * storage until informed by the frontend.
187 *
188 * @param app_directory Directory to use for application storage.
189 */
190void SetAppDirectory(const std::string& app_directory);
191
192/**
184 * Gets the filesystem path associated with the YuzuPath enum. 193 * Gets the filesystem path associated with the YuzuPath enum.
185 * 194 *
186 * @param yuzu_path YuzuPath enum 195 * @param yuzu_path YuzuPath enum
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index 01457d8c6..ba22595e0 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -11,6 +11,10 @@
11 11
12#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv 12#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
13 13
14#ifdef ANDROID
15#include <android/sharedmem.h>
16#endif
17
14#ifndef _GNU_SOURCE 18#ifndef _GNU_SOURCE
15#define _GNU_SOURCE 19#define _GNU_SOURCE
16#endif 20#endif
@@ -367,17 +371,20 @@ public:
367 } 371 }
368 372
369 // Backing memory initialization 373 // Backing memory initialization
370#if defined(__FreeBSD__) && __FreeBSD__ < 13 374#ifdef ANDROID
375 fd = ASharedMemory_create("HostMemory", backing_size);
376#elif defined(__FreeBSD__) && __FreeBSD__ < 13
371 // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30 377 // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30
372 fd = shm_open(SHM_ANON, O_RDWR, 0600); 378 fd = shm_open(SHM_ANON, O_RDWR, 0600);
373#else 379#else
374 fd = memfd_create("HostMemory", 0); 380 fd = memfd_create("HostMemory", 0);
375#endif 381#endif
376 if (fd == -1) { 382 if (fd < 0) {
377 LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno)); 383 LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno));
378 throw std::bad_alloc{}; 384 throw std::bad_alloc{};
379 } 385 }
380 386
387#ifndef ANDROID
381 // Defined to extend the file with zeros 388 // Defined to extend the file with zeros
382 int ret = ftruncate(fd, backing_size); 389 int ret = ftruncate(fd, backing_size);
383 if (ret != 0) { 390 if (ret != 0) {
@@ -385,6 +392,7 @@ public:
385 strerror(errno)); 392 strerror(errno));
386 throw std::bad_alloc{}; 393 throw std::bad_alloc{};
387 } 394 }
395#endif
388 396
389 backing_base = static_cast<u8*>( 397 backing_base = static_cast<u8*>(
390 mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); 398 mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index f96c7c222..6e8e8eb36 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -155,6 +155,26 @@ public:
155 void EnableForStacktrace() override {} 155 void EnableForStacktrace() override {}
156}; 156};
157 157
158#ifdef ANDROID
159/**
160 * Backend that writes to the Android logcat
161 */
162class LogcatBackend : public Backend {
163public:
164 explicit LogcatBackend() = default;
165
166 ~LogcatBackend() override = default;
167
168 void Write(const Entry& entry) override {
169 PrintMessageToLogcat(entry);
170 }
171
172 void Flush() override {}
173
174 void EnableForStacktrace() override {}
175};
176#endif
177
158bool initialization_in_progress_suppress_logging = true; 178bool initialization_in_progress_suppress_logging = true;
159 179
160/** 180/**
@@ -260,6 +280,9 @@ private:
260 lambda(static_cast<Backend&>(debugger_backend)); 280 lambda(static_cast<Backend&>(debugger_backend));
261 lambda(static_cast<Backend&>(color_console_backend)); 281 lambda(static_cast<Backend&>(color_console_backend));
262 lambda(static_cast<Backend&>(file_backend)); 282 lambda(static_cast<Backend&>(file_backend));
283#ifdef ANDROID
284 lambda(static_cast<Backend&>(lc_backend));
285#endif
263 } 286 }
264 287
265 static void Deleter(Impl* ptr) { 288 static void Deleter(Impl* ptr) {
@@ -272,6 +295,9 @@ private:
272 DebuggerBackend debugger_backend{}; 295 DebuggerBackend debugger_backend{};
273 ColorConsoleBackend color_console_backend{}; 296 ColorConsoleBackend color_console_backend{};
274 FileBackend file_backend; 297 FileBackend file_backend;
298#ifdef ANDROID
299 LogcatBackend lc_backend{};
300#endif
275 301
276 MPSCQueue<Entry> message_queue{}; 302 MPSCQueue<Entry> message_queue{};
277 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; 303 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index 09398ea64..2c453177b 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -8,6 +8,10 @@
8#include <windows.h> 8#include <windows.h>
9#endif 9#endif
10 10
11#ifdef ANDROID
12#include <android/log.h>
13#endif
14
11#include "common/assert.h" 15#include "common/assert.h"
12#include "common/logging/filter.h" 16#include "common/logging/filter.h"
13#include "common/logging/log.h" 17#include "common/logging/log.h"
@@ -106,4 +110,35 @@ void PrintColoredMessage(const Entry& entry) {
106#undef ESC 110#undef ESC
107#endif 111#endif
108} 112}
113
114void PrintMessageToLogcat(const Entry& entry) {
115#ifdef ANDROID
116 const auto str = FormatLogMessage(entry);
117
118 android_LogPriority android_log_priority;
119 switch (entry.log_level) {
120 case Level::Trace:
121 android_log_priority = ANDROID_LOG_VERBOSE;
122 break;
123 case Level::Debug:
124 android_log_priority = ANDROID_LOG_DEBUG;
125 break;
126 case Level::Info:
127 android_log_priority = ANDROID_LOG_INFO;
128 break;
129 case Level::Warning:
130 android_log_priority = ANDROID_LOG_WARN;
131 break;
132 case Level::Error:
133 android_log_priority = ANDROID_LOG_ERROR;
134 break;
135 case Level::Critical:
136 android_log_priority = ANDROID_LOG_FATAL;
137 break;
138 case Level::Count:
139 UNREACHABLE();
140 }
141 __android_log_print(android_log_priority, "YuzuNative", "%s", str.c_str());
142#endif
143}
109} // namespace Common::Log 144} // namespace Common::Log
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h
index 0d0ec4370..68417420b 100644
--- a/src/common/logging/text_formatter.h
+++ b/src/common/logging/text_formatter.h
@@ -15,4 +15,6 @@ std::string FormatLogMessage(const Entry& entry);
15void PrintMessage(const Entry& entry); 15void PrintMessage(const Entry& entry);
16/// Prints the same message as `PrintMessage`, but colored according to the severity level. 16/// Prints the same message as `PrintMessage`, but colored according to the severity level.
17void PrintColoredMessage(const Entry& entry); 17void PrintColoredMessage(const Entry& entry);
18/// Formats and prints a log entry to the android logcat.
19void PrintMessageToLogcat(const Entry& entry);
18} // namespace Common::Log 20} // namespace Common::Log
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 115fba27d..66dffc9bf 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -244,6 +244,7 @@ void RestoreGlobalState(bool is_powered_on) {
244 values.bg_green.SetGlobal(true); 244 values.bg_green.SetGlobal(true);
245 values.bg_blue.SetGlobal(true); 245 values.bg_blue.SetGlobal(true);
246 values.enable_compute_pipelines.SetGlobal(true); 246 values.enable_compute_pipelines.SetGlobal(true);
247 values.use_video_framerate.SetGlobal(true);
247 248
248 // System 249 // System
249 values.language_index.SetGlobal(true); 250 values.language_index.SetGlobal(true);
diff --git a/src/common/settings.h b/src/common/settings.h
index 7f865b2a7..9682281b0 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -482,6 +482,7 @@ struct Values {
482 SwitchableSetting<AstcRecompression, true> astc_recompression{ 482 SwitchableSetting<AstcRecompression, true> astc_recompression{
483 AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3, 483 AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
484 "astc_recompression"}; 484 "astc_recompression"};
485 SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
485 486
486 SwitchableSetting<u8> bg_red{0, "bg_red"}; 487 SwitchableSetting<u8> bg_red{0, "bg_red"};
487 SwitchableSetting<u8> bg_green{0, "bg_green"}; 488 SwitchableSetting<u8> bg_green{0, "bg_green"};
diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp
index 89e1ed225..035df7fe0 100644
--- a/src/common/uuid.cpp
+++ b/src/common/uuid.cpp
@@ -48,7 +48,7 @@ std::array<u8, 0x10> ConstructFromRawString(std::string_view raw_string) {
48} 48}
49 49
50std::array<u8, 0x10> ConstructFromFormattedString(std::string_view formatted_string) { 50std::array<u8, 0x10> ConstructFromFormattedString(std::string_view formatted_string) {
51 std::array<u8, 0x10> uuid; 51 std::array<u8, 0x10> uuid{};
52 52
53 size_t i = 0; 53 size_t i = 0;
54 54
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 8e5334e02..227c431bc 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -4,8 +4,6 @@
4add_library(core STATIC 4add_library(core STATIC
5 arm/arm_interface.h 5 arm/arm_interface.h
6 arm/arm_interface.cpp 6 arm/arm_interface.cpp
7 arm/dynarmic/arm_exclusive_monitor.cpp
8 arm/dynarmic/arm_exclusive_monitor.h
9 arm/exclusive_monitor.cpp 7 arm/exclusive_monitor.cpp
10 arm/exclusive_monitor.h 8 arm/exclusive_monitor.h
11 arm/symbols.cpp 9 arm/symbols.cpp
@@ -142,6 +140,7 @@ add_library(core STATIC
142 frontend/emu_window.h 140 frontend/emu_window.h
143 frontend/framebuffer_layout.cpp 141 frontend/framebuffer_layout.cpp
144 frontend/framebuffer_layout.h 142 frontend/framebuffer_layout.h
143 frontend/graphics_context.h
145 hid/emulated_console.cpp 144 hid/emulated_console.cpp
146 hid/emulated_console.h 145 hid/emulated_console.h
147 hid/emulated_controller.cpp 146 hid/emulated_controller.cpp
@@ -848,12 +847,15 @@ endif()
848 847
849if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) 848if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
850 target_sources(core PRIVATE 849 target_sources(core PRIVATE
850 arm/dynarmic/arm_dynarmic.h
851 arm/dynarmic/arm_dynarmic_64.cpp 851 arm/dynarmic/arm_dynarmic_64.cpp
852 arm/dynarmic/arm_dynarmic_64.h 852 arm/dynarmic/arm_dynarmic_64.h
853 arm/dynarmic/arm_dynarmic_32.cpp 853 arm/dynarmic/arm_dynarmic_32.cpp
854 arm/dynarmic/arm_dynarmic_32.h 854 arm/dynarmic/arm_dynarmic_32.h
855 arm/dynarmic/arm_dynarmic_cp15.cpp 855 arm/dynarmic/dynarmic_cp15.cpp
856 arm/dynarmic/arm_dynarmic_cp15.h 856 arm/dynarmic/dynarmic_cp15.h
857 arm/dynarmic/dynarmic_exclusive_monitor.cpp
858 arm/dynarmic/dynarmic_exclusive_monitor.h
857 hle/service/jit/jit_context.cpp 859 hle/service/jit/jit_context.cpp
858 hle/service/jit/jit_context.h 860 hle/service/jit/jit_context.h
859 hle/service/jit/jit.cpp 861 hle/service/jit/jit.cpp
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index d30914b7a..beaea64b3 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -13,25 +13,68 @@
13#include "core/core.h" 13#include "core/core.h"
14#include "core/debugger/debugger.h" 14#include "core/debugger/debugger.h"
15#include "core/hle/kernel/k_process.h" 15#include "core/hle/kernel/k_process.h"
16#include "core/hle/kernel/k_thread.h"
16#include "core/hle/kernel/svc.h" 17#include "core/hle/kernel/svc.h"
17#include "core/loader/loader.h" 18#include "core/loader/loader.h"
18#include "core/memory.h" 19#include "core/memory.h"
19 20
20#include "core/arm/dynarmic/arm_dynarmic_32.h"
21#include "core/arm/dynarmic/arm_dynarmic_64.h"
22
23namespace Core { 21namespace Core {
24 22
25constexpr u64 SEGMENT_BASE = 0x7100000000ull; 23constexpr u64 SEGMENT_BASE = 0x7100000000ull;
26 24
27std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext( 25std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext(
28 Core::System& system, const ARM_Interface::ThreadContext32& ctx) { 26 Core::System& system, const ARM_Interface::ThreadContext32& ctx) {
29 return ARM_Dynarmic_32::GetBacktraceFromContext(system, ctx); 27 std::vector<BacktraceEntry> out;
28 auto& memory = system.ApplicationMemory();
29
30 const auto& reg = ctx.cpu_registers;
31 u32 pc = reg[15], lr = reg[14], fp = reg[11];
32 out.push_back({"", 0, pc, 0, ""});
33
34 // fp (= r11) points to the last frame record.
35 // Frame records are two words long:
36 // fp+0 : pointer to previous frame record
37 // fp+4 : value of lr for frame
38 for (size_t i = 0; i < 256; i++) {
39 out.push_back({"", 0, lr, 0, ""});
40 if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 8)) {
41 break;
42 }
43 lr = memory.Read32(fp + 4);
44 fp = memory.Read32(fp);
45 }
46
47 SymbolicateBacktrace(system, out);
48
49 return out;
30} 50}
31 51
32std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext( 52std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext(
33 Core::System& system, const ARM_Interface::ThreadContext64& ctx) { 53 Core::System& system, const ARM_Interface::ThreadContext64& ctx) {
34 return ARM_Dynarmic_64::GetBacktraceFromContext(system, ctx); 54 std::vector<BacktraceEntry> out;
55 auto& memory = system.ApplicationMemory();
56
57 const auto& reg = ctx.cpu_registers;
58 u64 pc = ctx.pc, lr = reg[30], fp = reg[29];
59
60 out.push_back({"", 0, pc, 0, ""});
61
62 // fp (= x29) points to the previous frame record.
63 // Frame records are two words long:
64 // fp+0 : pointer to previous frame record
65 // fp+8 : value of lr for frame
66 for (size_t i = 0; i < 256; i++) {
67 out.push_back({"", 0, lr, 0, ""});
68 if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 16)) {
69 break;
70 }
71 lr = memory.Read64(fp + 8);
72 fp = memory.Read64(fp);
73 }
74
75 SymbolicateBacktrace(system, out);
76
77 return out;
35} 78}
36 79
37void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out) { 80void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out) {
@@ -76,6 +119,18 @@ void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<Backt
76 } 119 }
77} 120}
78 121
122std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
123 if (GetArchitecture() == Architecture::Aarch64) {
124 ThreadContext64 ctx;
125 SaveContext(ctx);
126 return GetBacktraceFromContext(system, ctx);
127 } else {
128 ThreadContext32 ctx;
129 SaveContext(ctx);
130 return GetBacktraceFromContext(system, ctx);
131 }
132}
133
79void ARM_Interface::LogBacktrace() const { 134void ARM_Interface::LogBacktrace() const {
80 const VAddr sp = GetSP(); 135 const VAddr sp = GetSP();
81 const VAddr pc = GetPC(); 136 const VAddr pc = GetPC();
@@ -83,7 +138,6 @@ void ARM_Interface::LogBacktrace() const {
83 LOG_ERROR(Core_ARM, "{:20}{:20}{:20}{:20}{}", "Module Name", "Address", "Original Address", 138 LOG_ERROR(Core_ARM, "{:20}{:20}{:20}{:20}{}", "Module Name", "Address", "Original Address",
84 "Offset", "Symbol"); 139 "Offset", "Symbol");
85 LOG_ERROR(Core_ARM, ""); 140 LOG_ERROR(Core_ARM, "");
86
87 const auto backtrace = GetBacktrace(); 141 const auto backtrace = GetBacktrace();
88 for (const auto& entry : backtrace) { 142 for (const auto& entry : backtrace) {
89 LOG_ERROR(Core_ARM, "{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address, 143 LOG_ERROR(Core_ARM, "{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address,
@@ -97,7 +151,7 @@ void ARM_Interface::Run() {
97 151
98 while (true) { 152 while (true) {
99 Kernel::KThread* current_thread{Kernel::GetCurrentThreadPointer(system.Kernel())}; 153 Kernel::KThread* current_thread{Kernel::GetCurrentThreadPointer(system.Kernel())};
100 Dynarmic::HaltReason hr{}; 154 HaltReason hr{};
101 155
102 // Notify the debugger and go to sleep if a step was performed 156 // Notify the debugger and go to sleep if a step was performed
103 // and this thread has been scheduled again. 157 // and this thread has been scheduled again.
@@ -108,17 +162,17 @@ void ARM_Interface::Run() {
108 } 162 }
109 163
110 // Otherwise, run the thread. 164 // Otherwise, run the thread.
111 system.EnterDynarmicProfile(); 165 system.EnterCPUProfile();
112 if (current_thread->GetStepState() == StepState::StepPending) { 166 if (current_thread->GetStepState() == StepState::StepPending) {
113 hr = StepJit(); 167 hr = StepJit();
114 168
115 if (Has(hr, step_thread)) { 169 if (True(hr & HaltReason::StepThread)) {
116 current_thread->SetStepState(StepState::StepPerformed); 170 current_thread->SetStepState(StepState::StepPerformed);
117 } 171 }
118 } else { 172 } else {
119 hr = RunJit(); 173 hr = RunJit();
120 } 174 }
121 system.ExitDynarmicProfile(); 175 system.ExitCPUProfile();
122 176
123 // If the thread is scheduled for termination, exit the thread. 177 // If the thread is scheduled for termination, exit the thread.
124 if (current_thread->HasDpc()) { 178 if (current_thread->HasDpc()) {
@@ -130,8 +184,8 @@ void ARM_Interface::Run() {
130 184
131 // Notify the debugger and go to sleep if a breakpoint was hit, 185 // Notify the debugger and go to sleep if a breakpoint was hit,
132 // or if the thread is unable to continue for any reason. 186 // or if the thread is unable to continue for any reason.
133 if (Has(hr, breakpoint) || Has(hr, no_execute)) { 187 if (True(hr & HaltReason::InstructionBreakpoint) || True(hr & HaltReason::PrefetchAbort)) {
134 if (!Has(hr, no_execute)) { 188 if (!True(hr & HaltReason::InstructionBreakpoint)) {
135 RewindBreakpointInstruction(); 189 RewindBreakpointInstruction();
136 } 190 }
137 if (system.DebuggerEnabled()) { 191 if (system.DebuggerEnabled()) {
@@ -144,7 +198,7 @@ void ARM_Interface::Run() {
144 } 198 }
145 199
146 // Notify the debugger and go to sleep if a watchpoint was hit. 200 // Notify the debugger and go to sleep if a watchpoint was hit.
147 if (Has(hr, watchpoint)) { 201 if (True(hr & HaltReason::DataAbort)) {
148 if (system.DebuggerEnabled()) { 202 if (system.DebuggerEnabled()) {
149 system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint()); 203 system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint());
150 } 204 }
@@ -153,11 +207,11 @@ void ARM_Interface::Run() {
153 } 207 }
154 208
155 // Handle syscalls and scheduling (this may change the current thread/core) 209 // Handle syscalls and scheduling (this may change the current thread/core)
156 if (Has(hr, svc_call)) { 210 if (True(hr & HaltReason::SupervisorCall)) {
157 Kernel::Svc::Call(system, GetSvcNumber()); 211 Kernel::Svc::Call(system, GetSvcNumber());
158 break; 212 break;
159 } 213 }
160 if (Has(hr, break_loop) || !uses_wall_clock) { 214 if (True(hr & HaltReason::BreakLoop) || !uses_wall_clock) {
161 break; 215 break;
162 } 216 }
163 } 217 }
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 8e40702cc..d5f2fa09a 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -8,8 +8,6 @@
8#include <string> 8#include <string>
9#include <vector> 9#include <vector>
10 10
11#include <dynarmic/interface/halt_reason.h>
12
13#include "common/common_funcs.h" 11#include "common/common_funcs.h"
14#include "common/common_types.h" 12#include "common/common_types.h"
15#include "core/hardware_properties.h" 13#include "core/hardware_properties.h"
@@ -30,6 +28,22 @@ class CPUInterruptHandler;
30 28
31using WatchpointArray = std::array<Kernel::DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>; 29using WatchpointArray = std::array<Kernel::DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>;
32 30
31// NOTE: these values match the HaltReason enum in Dynarmic
32enum class HaltReason : u64 {
33 StepThread = 0x00000001,
34 DataAbort = 0x00000004,
35 BreakLoop = 0x02000000,
36 SupervisorCall = 0x04000000,
37 InstructionBreakpoint = 0x08000000,
38 PrefetchAbort = 0x20000000,
39};
40DECLARE_ENUM_FLAG_OPERATORS(HaltReason);
41
42enum class Architecture {
43 Aarch32,
44 Aarch64,
45};
46
33/// Generic ARMv8 CPU interface 47/// Generic ARMv8 CPU interface
34class ARM_Interface { 48class ARM_Interface {
35public: 49public:
@@ -167,8 +181,9 @@ public:
167 */ 181 */
168 virtual void SetTPIDR_EL0(u64 value) = 0; 182 virtual void SetTPIDR_EL0(u64 value) = 0;
169 183
170 virtual void SaveContext(ThreadContext32& ctx) = 0; 184 virtual Architecture GetArchitecture() const = 0;
171 virtual void SaveContext(ThreadContext64& ctx) = 0; 185 virtual void SaveContext(ThreadContext32& ctx) const = 0;
186 virtual void SaveContext(ThreadContext64& ctx) const = 0;
172 virtual void LoadContext(const ThreadContext32& ctx) = 0; 187 virtual void LoadContext(const ThreadContext32& ctx) = 0;
173 virtual void LoadContext(const ThreadContext64& ctx) = 0; 188 virtual void LoadContext(const ThreadContext64& ctx) = 0;
174 void LoadWatchpointArray(const WatchpointArray& wp); 189 void LoadWatchpointArray(const WatchpointArray& wp);
@@ -195,17 +210,9 @@ public:
195 static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system, 210 static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
196 const ThreadContext64& ctx); 211 const ThreadContext64& ctx);
197 212
198 virtual std::vector<BacktraceEntry> GetBacktrace() const = 0; 213 std::vector<BacktraceEntry> GetBacktrace() const;
199
200 void LogBacktrace() const; 214 void LogBacktrace() const;
201 215
202 static constexpr Dynarmic::HaltReason step_thread = Dynarmic::HaltReason::Step;
203 static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
204 static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
205 static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
206 static constexpr Dynarmic::HaltReason watchpoint = Dynarmic::HaltReason::MemoryAbort;
207 static constexpr Dynarmic::HaltReason no_execute = Dynarmic::HaltReason::UserDefined6;
208
209protected: 216protected:
210 /// System context that this ARM interface is running under. 217 /// System context that this ARM interface is running under.
211 System& system; 218 System& system;
@@ -216,8 +223,8 @@ protected:
216 const Kernel::DebugWatchpoint* MatchingWatchpoint( 223 const Kernel::DebugWatchpoint* MatchingWatchpoint(
217 u64 addr, u64 size, Kernel::DebugWatchpointType access_type) const; 224 u64 addr, u64 size, Kernel::DebugWatchpointType access_type) const;
218 225
219 virtual Dynarmic::HaltReason RunJit() = 0; 226 virtual HaltReason RunJit() = 0;
220 virtual Dynarmic::HaltReason StepJit() = 0; 227 virtual HaltReason StepJit() = 0;
221 virtual u32 GetSvcNumber() const = 0; 228 virtual u32 GetSvcNumber() const = 0;
222 virtual const Kernel::DebugWatchpoint* HaltedWatchpoint() const = 0; 229 virtual const Kernel::DebugWatchpoint* HaltedWatchpoint() const = 0;
223 virtual void RewindBreakpointInstruction() = 0; 230 virtual void RewindBreakpointInstruction() = 0;
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
new file mode 100644
index 000000000..eef7c3116
--- /dev/null
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -0,0 +1,29 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <dynarmic/interface/halt_reason.h>
5
6#include "core/arm/arm_interface.h"
7
8namespace Core {
9
10constexpr Dynarmic::HaltReason StepThread = Dynarmic::HaltReason::Step;
11constexpr Dynarmic::HaltReason DataAbort = Dynarmic::HaltReason::MemoryAbort;
12constexpr Dynarmic::HaltReason BreakLoop = Dynarmic::HaltReason::UserDefined2;
13constexpr Dynarmic::HaltReason SupervisorCall = Dynarmic::HaltReason::UserDefined3;
14constexpr Dynarmic::HaltReason InstructionBreakpoint = Dynarmic::HaltReason::UserDefined4;
15constexpr Dynarmic::HaltReason PrefetchAbort = Dynarmic::HaltReason::UserDefined6;
16
17constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
18 static_assert(static_cast<u64>(HaltReason::StepThread) == static_cast<u64>(StepThread));
19 static_assert(static_cast<u64>(HaltReason::DataAbort) == static_cast<u64>(DataAbort));
20 static_assert(static_cast<u64>(HaltReason::BreakLoop) == static_cast<u64>(BreakLoop));
21 static_assert(static_cast<u64>(HaltReason::SupervisorCall) == static_cast<u64>(SupervisorCall));
22 static_assert(static_cast<u64>(HaltReason::InstructionBreakpoint) ==
23 static_cast<u64>(InstructionBreakpoint));
24 static_assert(static_cast<u64>(HaltReason::PrefetchAbort) == static_cast<u64>(PrefetchAbort));
25
26 return static_cast<HaltReason>(hr);
27}
28
29} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index dfdcbe35a..5acf9008d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -10,9 +10,10 @@
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/page_table.h" 11#include "common/page_table.h"
12#include "common/settings.h" 12#include "common/settings.h"
13#include "core/arm/dynarmic/arm_dynarmic.h"
13#include "core/arm/dynarmic/arm_dynarmic_32.h" 14#include "core/arm/dynarmic/arm_dynarmic_32.h"
14#include "core/arm/dynarmic/arm_dynarmic_cp15.h" 15#include "core/arm/dynarmic/dynarmic_cp15.h"
15#include "core/arm/dynarmic/arm_exclusive_monitor.h" 16#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
16#include "core/core.h" 17#include "core/core.h"
17#include "core/core_timing.h" 18#include "core/core_timing.h"
18#include "core/debugger/debugger.h" 19#include "core/debugger/debugger.h"
@@ -104,11 +105,11 @@ public:
104 switch (exception) { 105 switch (exception) {
105 case Dynarmic::A32::Exception::NoExecuteFault: 106 case Dynarmic::A32::Exception::NoExecuteFault:
106 LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#08x}", pc); 107 LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#08x}", pc);
107 ReturnException(pc, ARM_Interface::no_execute); 108 ReturnException(pc, PrefetchAbort);
108 return; 109 return;
109 default: 110 default:
110 if (debugger_enabled) { 111 if (debugger_enabled) {
111 ReturnException(pc, ARM_Interface::breakpoint); 112 ReturnException(pc, InstructionBreakpoint);
112 return; 113 return;
113 } 114 }
114 115
@@ -121,7 +122,7 @@ public:
121 122
122 void CallSVC(u32 swi) override { 123 void CallSVC(u32 swi) override {
123 parent.svc_swi = swi; 124 parent.svc_swi = swi;
124 parent.jit.load()->HaltExecution(ARM_Interface::svc_call); 125 parent.jit.load()->HaltExecution(SupervisorCall);
125 } 126 }
126 127
127 void AddTicks(u64 ticks) override { 128 void AddTicks(u64 ticks) override {
@@ -162,7 +163,7 @@ public:
162 if (!memory.IsValidVirtualAddressRange(addr, size)) { 163 if (!memory.IsValidVirtualAddressRange(addr, size)) {
163 LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}", 164 LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}",
164 addr); 165 addr);
165 parent.jit.load()->HaltExecution(ARM_Interface::no_execute); 166 parent.jit.load()->HaltExecution(PrefetchAbort);
166 return false; 167 return false;
167 } 168 }
168 169
@@ -173,7 +174,7 @@ public:
173 const auto match{parent.MatchingWatchpoint(addr, size, type)}; 174 const auto match{parent.MatchingWatchpoint(addr, size, type)};
174 if (match) { 175 if (match) {
175 parent.halted_watchpoint = match; 176 parent.halted_watchpoint = match;
176 parent.jit.load()->HaltExecution(ARM_Interface::watchpoint); 177 parent.jit.load()->HaltExecution(DataAbort);
177 return false; 178 return false;
178 } 179 }
179 180
@@ -329,12 +330,12 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
329 return std::make_unique<Dynarmic::A32::Jit>(config); 330 return std::make_unique<Dynarmic::A32::Jit>(config);
330} 331}
331 332
332Dynarmic::HaltReason ARM_Dynarmic_32::RunJit() { 333HaltReason ARM_Dynarmic_32::RunJit() {
333 return jit.load()->Run(); 334 return TranslateHaltReason(jit.load()->Run());
334} 335}
335 336
336Dynarmic::HaltReason ARM_Dynarmic_32::StepJit() { 337HaltReason ARM_Dynarmic_32::StepJit() {
337 return jit.load()->Step(); 338 return TranslateHaltReason(jit.load()->Step());
338} 339}
339 340
340u32 ARM_Dynarmic_32::GetSvcNumber() const { 341u32 ARM_Dynarmic_32::GetSvcNumber() const {
@@ -408,7 +409,7 @@ void ARM_Dynarmic_32::SetTPIDR_EL0(u64 value) {
408 cp15->uprw = static_cast<u32>(value); 409 cp15->uprw = static_cast<u32>(value);
409} 410}
410 411
411void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) { 412void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) const {
412 Dynarmic::A32::Jit* j = jit.load(); 413 Dynarmic::A32::Jit* j = jit.load();
413 ctx.cpu_registers = j->Regs(); 414 ctx.cpu_registers = j->Regs();
414 ctx.extension_registers = j->ExtRegs(); 415 ctx.extension_registers = j->ExtRegs();
@@ -425,11 +426,11 @@ void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) {
425} 426}
426 427
427void ARM_Dynarmic_32::SignalInterrupt() { 428void ARM_Dynarmic_32::SignalInterrupt() {
428 jit.load()->HaltExecution(break_loop); 429 jit.load()->HaltExecution(BreakLoop);
429} 430}
430 431
431void ARM_Dynarmic_32::ClearInterrupt() { 432void ARM_Dynarmic_32::ClearInterrupt() {
432 jit.load()->ClearHalt(break_loop); 433 jit.load()->ClearHalt(BreakLoop);
433} 434}
434 435
435void ARM_Dynarmic_32::ClearInstructionCache() { 436void ARM_Dynarmic_32::ClearInstructionCache() {
@@ -462,39 +463,4 @@ void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table,
462 jit_cache.emplace(key, std::move(new_jit)); 463 jit_cache.emplace(key, std::move(new_jit));
463} 464}
464 465
465std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktrace(Core::System& system,
466 u64 fp, u64 lr, u64 pc) {
467 std::vector<BacktraceEntry> out;
468 auto& memory = system.ApplicationMemory();
469
470 out.push_back({"", 0, pc, 0, ""});
471
472 // fp (= r11) points to the last frame record.
473 // Frame records are two words long:
474 // fp+0 : pointer to previous frame record
475 // fp+4 : value of lr for frame
476 for (size_t i = 0; i < 256; i++) {
477 out.push_back({"", 0, lr, 0, ""});
478 if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 8)) {
479 break;
480 }
481 lr = memory.Read32(fp + 4);
482 fp = memory.Read32(fp);
483 }
484
485 SymbolicateBacktrace(system, out);
486
487 return out;
488}
489
490std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktraceFromContext(
491 System& system, const ThreadContext32& ctx) {
492 const auto& reg = ctx.cpu_registers;
493 return GetBacktrace(system, reg[11], reg[14], reg[15]);
494}
495
496std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktrace() const {
497 return GetBacktrace(system, GetReg(11), GetReg(14), GetReg(15));
498}
499
500} // namespace Core 466} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h
index bce695daf..a990845cb 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -50,8 +50,11 @@ public:
50 return (GetPSTATE() & 0x20) != 0; 50 return (GetPSTATE() & 0x20) != 0;
51 } 51 }
52 52
53 void SaveContext(ThreadContext32& ctx) override; 53 Architecture GetArchitecture() const override {
54 void SaveContext(ThreadContext64& ctx) override {} 54 return Architecture::Aarch32;
55 }
56 void SaveContext(ThreadContext32& ctx) const override;
57 void SaveContext(ThreadContext64& ctx) const override {}
55 void LoadContext(const ThreadContext32& ctx) override; 58 void LoadContext(const ThreadContext32& ctx) override;
56 void LoadContext(const ThreadContext64& ctx) override {} 59 void LoadContext(const ThreadContext64& ctx) override {}
57 60
@@ -64,14 +67,9 @@ public:
64 void PageTableChanged(Common::PageTable& new_page_table, 67 void PageTableChanged(Common::PageTable& new_page_table,
65 std::size_t new_address_space_size_in_bits) override; 68 std::size_t new_address_space_size_in_bits) override;
66 69
67 static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
68 const ThreadContext32& ctx);
69
70 std::vector<BacktraceEntry> GetBacktrace() const override;
71
72protected: 70protected:
73 Dynarmic::HaltReason RunJit() override; 71 HaltReason RunJit() override;
74 Dynarmic::HaltReason StepJit() override; 72 HaltReason StepJit() override;
75 u32 GetSvcNumber() const override; 73 u32 GetSvcNumber() const override;
76 const Kernel::DebugWatchpoint* HaltedWatchpoint() const override; 74 const Kernel::DebugWatchpoint* HaltedWatchpoint() const override;
77 void RewindBreakpointInstruction() override; 75 void RewindBreakpointInstruction() override;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index bbbcb4f9d..bb97ed5bc 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -10,8 +10,9 @@
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/page_table.h" 11#include "common/page_table.h"
12#include "common/settings.h" 12#include "common/settings.h"
13#include "core/arm/dynarmic/arm_dynarmic.h"
13#include "core/arm/dynarmic/arm_dynarmic_64.h" 14#include "core/arm/dynarmic/arm_dynarmic_64.h"
14#include "core/arm/dynarmic/arm_exclusive_monitor.h" 15#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
15#include "core/core.h" 16#include "core/core.h"
16#include "core/core_timing.h" 17#include "core/core_timing.h"
17#include "core/debugger/debugger.h" 18#include "core/debugger/debugger.h"
@@ -113,7 +114,7 @@ public:
113 LOG_ERROR(Core_ARM, 114 LOG_ERROR(Core_ARM,
114 "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc, 115 "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc,
115 num_instructions, memory.Read32(pc)); 116 num_instructions, memory.Read32(pc));
116 ReturnException(pc, ARM_Interface::no_execute); 117 ReturnException(pc, PrefetchAbort);
117 } 118 }
118 119
119 void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, 120 void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
@@ -148,11 +149,11 @@ public:
148 return; 149 return;
149 case Dynarmic::A64::Exception::NoExecuteFault: 150 case Dynarmic::A64::Exception::NoExecuteFault:
150 LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc); 151 LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc);
151 ReturnException(pc, ARM_Interface::no_execute); 152 ReturnException(pc, PrefetchAbort);
152 return; 153 return;
153 default: 154 default:
154 if (debugger_enabled) { 155 if (debugger_enabled) {
155 ReturnException(pc, ARM_Interface::breakpoint); 156 ReturnException(pc, InstructionBreakpoint);
156 return; 157 return;
157 } 158 }
158 159
@@ -164,7 +165,7 @@ public:
164 165
165 void CallSVC(u32 swi) override { 166 void CallSVC(u32 swi) override {
166 parent.svc_swi = swi; 167 parent.svc_swi = swi;
167 parent.jit.load()->HaltExecution(ARM_Interface::svc_call); 168 parent.jit.load()->HaltExecution(SupervisorCall);
168 } 169 }
169 170
170 void AddTicks(u64 ticks) override { 171 void AddTicks(u64 ticks) override {
@@ -207,7 +208,7 @@ public:
207 if (!memory.IsValidVirtualAddressRange(addr, size)) { 208 if (!memory.IsValidVirtualAddressRange(addr, size)) {
208 LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}", 209 LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}",
209 addr); 210 addr);
210 parent.jit.load()->HaltExecution(ARM_Interface::no_execute); 211 parent.jit.load()->HaltExecution(PrefetchAbort);
211 return false; 212 return false;
212 } 213 }
213 214
@@ -218,7 +219,7 @@ public:
218 const auto match{parent.MatchingWatchpoint(addr, size, type)}; 219 const auto match{parent.MatchingWatchpoint(addr, size, type)};
219 if (match) { 220 if (match) {
220 parent.halted_watchpoint = match; 221 parent.halted_watchpoint = match;
221 parent.jit.load()->HaltExecution(ARM_Interface::watchpoint); 222 parent.jit.load()->HaltExecution(DataAbort);
222 return false; 223 return false;
223 } 224 }
224 225
@@ -383,12 +384,12 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
383 return std::make_shared<Dynarmic::A64::Jit>(config); 384 return std::make_shared<Dynarmic::A64::Jit>(config);
384} 385}
385 386
386Dynarmic::HaltReason ARM_Dynarmic_64::RunJit() { 387HaltReason ARM_Dynarmic_64::RunJit() {
387 return jit.load()->Run(); 388 return TranslateHaltReason(jit.load()->Run());
388} 389}
389 390
390Dynarmic::HaltReason ARM_Dynarmic_64::StepJit() { 391HaltReason ARM_Dynarmic_64::StepJit() {
391 return jit.load()->Step(); 392 return TranslateHaltReason(jit.load()->Step());
392} 393}
393 394
394u32 ARM_Dynarmic_64::GetSvcNumber() const { 395u32 ARM_Dynarmic_64::GetSvcNumber() const {
@@ -464,7 +465,7 @@ void ARM_Dynarmic_64::SetTPIDR_EL0(u64 value) {
464 cb->tpidr_el0 = value; 465 cb->tpidr_el0 = value;
465} 466}
466 467
467void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) { 468void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) const {
468 Dynarmic::A64::Jit* j = jit.load(); 469 Dynarmic::A64::Jit* j = jit.load();
469 ctx.cpu_registers = j->GetRegisters(); 470 ctx.cpu_registers = j->GetRegisters();
470 ctx.sp = j->GetSP(); 471 ctx.sp = j->GetSP();
@@ -489,11 +490,11 @@ void ARM_Dynarmic_64::LoadContext(const ThreadContext64& ctx) {
489} 490}
490 491
491void ARM_Dynarmic_64::SignalInterrupt() { 492void ARM_Dynarmic_64::SignalInterrupt() {
492 jit.load()->HaltExecution(break_loop); 493 jit.load()->HaltExecution(BreakLoop);
493} 494}
494 495
495void ARM_Dynarmic_64::ClearInterrupt() { 496void ARM_Dynarmic_64::ClearInterrupt() {
496 jit.load()->ClearHalt(break_loop); 497 jit.load()->ClearHalt(BreakLoop);
497} 498}
498 499
499void ARM_Dynarmic_64::ClearInstructionCache() { 500void ARM_Dynarmic_64::ClearInstructionCache() {
@@ -526,39 +527,4 @@ void ARM_Dynarmic_64::PageTableChanged(Common::PageTable& page_table,
526 jit_cache.emplace(key, std::move(new_jit)); 527 jit_cache.emplace(key, std::move(new_jit));
527} 528}
528 529
529std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktrace(Core::System& system,
530 u64 fp, u64 lr, u64 pc) {
531 std::vector<BacktraceEntry> out;
532 auto& memory = system.ApplicationMemory();
533
534 out.push_back({"", 0, pc, 0, ""});
535
536 // fp (= x29) points to the previous frame record.
537 // Frame records are two words long:
538 // fp+0 : pointer to previous frame record
539 // fp+8 : value of lr for frame
540 for (size_t i = 0; i < 256; i++) {
541 out.push_back({"", 0, lr, 0, ""});
542 if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 16)) {
543 break;
544 }
545 lr = memory.Read64(fp + 8);
546 fp = memory.Read64(fp);
547 }
548
549 SymbolicateBacktrace(system, out);
550
551 return out;
552}
553
554std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktraceFromContext(
555 System& system, const ThreadContext64& ctx) {
556 const auto& reg = ctx.cpu_registers;
557 return GetBacktrace(system, reg[29], reg[30], ctx.pc);
558}
559
560std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktrace() const {
561 return GetBacktrace(system, GetReg(29), GetReg(30), GetPC());
562}
563
564} // namespace Core 530} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index e83599e82..af2aa1f1c 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -43,8 +43,11 @@ public:
43 void SetTPIDR_EL0(u64 value) override; 43 void SetTPIDR_EL0(u64 value) override;
44 u64 GetTPIDR_EL0() const override; 44 u64 GetTPIDR_EL0() const override;
45 45
46 void SaveContext(ThreadContext32& ctx) override {} 46 Architecture GetArchitecture() const override {
47 void SaveContext(ThreadContext64& ctx) override; 47 return Architecture::Aarch64;
48 }
49 void SaveContext(ThreadContext32& ctx) const override {}
50 void SaveContext(ThreadContext64& ctx) const override;
48 void LoadContext(const ThreadContext32& ctx) override {} 51 void LoadContext(const ThreadContext32& ctx) override {}
49 void LoadContext(const ThreadContext64& ctx) override; 52 void LoadContext(const ThreadContext64& ctx) override;
50 53
@@ -57,14 +60,9 @@ public:
57 void PageTableChanged(Common::PageTable& new_page_table, 60 void PageTableChanged(Common::PageTable& new_page_table,
58 std::size_t new_address_space_size_in_bits) override; 61 std::size_t new_address_space_size_in_bits) override;
59 62
60 static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
61 const ThreadContext64& ctx);
62
63 std::vector<BacktraceEntry> GetBacktrace() const override;
64
65protected: 63protected:
66 Dynarmic::HaltReason RunJit() override; 64 HaltReason RunJit() override;
67 Dynarmic::HaltReason StepJit() override; 65 HaltReason StepJit() override;
68 u32 GetSvcNumber() const override; 66 u32 GetSvcNumber() const override;
69 const Kernel::DebugWatchpoint* HaltedWatchpoint() const override; 67 const Kernel::DebugWatchpoint* HaltedWatchpoint() const override;
70 void RewindBreakpointInstruction() override; 68 void RewindBreakpointInstruction() override;
@@ -73,8 +71,6 @@ private:
73 std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table, 71 std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
74 std::size_t address_space_bits) const; 72 std::size_t address_space_bits) const;
75 73
76 static std::vector<BacktraceEntry> GetBacktrace(Core::System& system, u64 fp, u64 lr, u64 pc);
77
78 using JitCacheKey = std::pair<Common::PageTable*, std::size_t>; 74 using JitCacheKey = std::pair<Common::PageTable*, std::size_t>;
79 using JitCacheType = 75 using JitCacheType =
80 std::unordered_map<JitCacheKey, std::shared_ptr<Dynarmic::A64::Jit>, Common::PairHash>; 76 std::unordered_map<JitCacheKey, std::shared_ptr<Dynarmic::A64::Jit>, Common::PairHash>;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/dynarmic_cp15.cpp
index 5a4eba3eb..92c548db0 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
+++ b/src/core/arm/dynarmic/dynarmic_cp15.cpp
@@ -4,7 +4,7 @@
4#include <fmt/format.h> 4#include <fmt/format.h>
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "core/arm/dynarmic/arm_dynarmic_32.h" 6#include "core/arm/dynarmic/arm_dynarmic_32.h"
7#include "core/arm/dynarmic/arm_dynarmic_cp15.h" 7#include "core/arm/dynarmic/dynarmic_cp15.h"
8#include "core/core.h" 8#include "core/core.h"
9#include "core/core_timing.h" 9#include "core/core_timing.h"
10 10
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/dynarmic_cp15.h
index d90b3e568..d90b3e568 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h
+++ b/src/core/arm/dynarmic/dynarmic_cp15.h
diff --git a/src/core/arm/dynarmic/arm_exclusive_monitor.cpp b/src/core/arm/dynarmic/dynarmic_exclusive_monitor.cpp
index fa0c48b25..b5c9c43c4 100644
--- a/src/core/arm/dynarmic/arm_exclusive_monitor.cpp
+++ b/src/core/arm/dynarmic/dynarmic_exclusive_monitor.cpp
@@ -1,7 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/arm/dynarmic/arm_exclusive_monitor.h" 4#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
5#include "core/memory.h" 5#include "core/memory.h"
6 6
7namespace Core { 7namespace Core {
diff --git a/src/core/arm/dynarmic/arm_exclusive_monitor.h b/src/core/arm/dynarmic/dynarmic_exclusive_monitor.h
index 57e6dd0d0..57e6dd0d0 100644
--- a/src/core/arm/dynarmic/arm_exclusive_monitor.h
+++ b/src/core/arm/dynarmic/dynarmic_exclusive_monitor.h
diff --git a/src/core/arm/exclusive_monitor.cpp b/src/core/arm/exclusive_monitor.cpp
index 20550faeb..6d9a862e1 100644
--- a/src/core/arm/exclusive_monitor.cpp
+++ b/src/core/arm/exclusive_monitor.cpp
@@ -2,7 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64) 4#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
5#include "core/arm/dynarmic/arm_exclusive_monitor.h" 5#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
6#endif 6#endif
7#include "core/arm/exclusive_monitor.h" 7#include "core/arm/exclusive_monitor.h"
8#include "core/memory.h" 8#include "core/memory.h"
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 4406ae30e..b74fd0a58 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -54,10 +54,10 @@
54#include "video_core/renderer_base.h" 54#include "video_core/renderer_base.h"
55#include "video_core/video_core.h" 55#include "video_core/video_core.h"
56 56
57MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU0, "ARM JIT", "Dynarmic CPU 0", MP_RGB(255, 64, 64)); 57MICROPROFILE_DEFINE(ARM_CPU0, "ARM", "CPU 0", MP_RGB(255, 64, 64));
58MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU1, "ARM JIT", "Dynarmic CPU 1", MP_RGB(255, 64, 64)); 58MICROPROFILE_DEFINE(ARM_CPU1, "ARM", "CPU 1", MP_RGB(255, 64, 64));
59MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU2, "ARM JIT", "Dynarmic CPU 2", MP_RGB(255, 64, 64)); 59MICROPROFILE_DEFINE(ARM_CPU2, "ARM", "CPU 2", MP_RGB(255, 64, 64));
60MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU3, "ARM JIT", "Dynarmic CPU 3", MP_RGB(255, 64, 64)); 60MICROPROFILE_DEFINE(ARM_CPU3, "ARM", "CPU 3", MP_RGB(255, 64, 64));
61 61
62namespace Core { 62namespace Core {
63 63
@@ -216,6 +216,14 @@ struct System::Impl {
216 } 216 }
217 } 217 }
218 218
219 void SetNVDECActive(bool is_nvdec_active) {
220 nvdec_active = is_nvdec_active;
221 }
222
223 bool GetNVDECActive() {
224 return nvdec_active;
225 }
226
219 void InitializeDebugger(System& system, u16 port) { 227 void InitializeDebugger(System& system, u16 port) {
220 debugger = std::make_unique<Debugger>(system, port); 228 debugger = std::make_unique<Debugger>(system, port);
221 } 229 }
@@ -251,10 +259,10 @@ struct System::Impl {
251 is_powered_on = true; 259 is_powered_on = true;
252 exit_lock = false; 260 exit_lock = false;
253 261
254 microprofile_dynarmic[0] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU0); 262 microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0);
255 microprofile_dynarmic[1] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU1); 263 microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1);
256 microprofile_dynarmic[2] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU2); 264 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
257 microprofile_dynarmic[3] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU3); 265 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
258 266
259 LOG_DEBUG(Core, "Initialized OK"); 267 LOG_DEBUG(Core, "Initialized OK");
260 268
@@ -485,6 +493,8 @@ struct System::Impl {
485 std::atomic_bool is_powered_on{}; 493 std::atomic_bool is_powered_on{};
486 bool exit_lock = false; 494 bool exit_lock = false;
487 495
496 bool nvdec_active{};
497
488 Reporter reporter; 498 Reporter reporter;
489 std::unique_ptr<Memory::CheatEngine> cheat_engine; 499 std::unique_ptr<Memory::CheatEngine> cheat_engine;
490 std::unique_ptr<Tools::Freezer> memory_freezer; 500 std::unique_ptr<Tools::Freezer> memory_freezer;
@@ -529,7 +539,7 @@ struct System::Impl {
529 ExitCallback exit_callback; 539 ExitCallback exit_callback;
530 540
531 std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{}; 541 std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
532 std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{}; 542 std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
533}; 543};
534 544
535System::System() : impl{std::make_unique<Impl>(*this)} {} 545System::System() : impl{std::make_unique<Impl>(*this)} {}
@@ -594,6 +604,14 @@ void System::UnstallApplication() {
594 impl->UnstallApplication(); 604 impl->UnstallApplication();
595} 605}
596 606
607void System::SetNVDECActive(bool is_nvdec_active) {
608 impl->SetNVDECActive(is_nvdec_active);
609}
610
611bool System::GetNVDECActive() {
612 return impl->GetNVDECActive();
613}
614
597void System::InitializeDebugger() { 615void System::InitializeDebugger() {
598 impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue()); 616 impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
599} 617}
@@ -909,14 +927,14 @@ void System::RegisterHostThread() {
909 impl->kernel.RegisterHostThread(); 927 impl->kernel.RegisterHostThread();
910} 928}
911 929
912void System::EnterDynarmicProfile() { 930void System::EnterCPUProfile() {
913 std::size_t core = impl->kernel.GetCurrentHostThreadID(); 931 std::size_t core = impl->kernel.GetCurrentHostThreadID();
914 impl->dynarmic_ticks[core] = MicroProfileEnter(impl->microprofile_dynarmic[core]); 932 impl->dynarmic_ticks[core] = MicroProfileEnter(impl->microprofile_cpu[core]);
915} 933}
916 934
917void System::ExitDynarmicProfile() { 935void System::ExitCPUProfile() {
918 std::size_t core = impl->kernel.GetCurrentHostThreadID(); 936 std::size_t core = impl->kernel.GetCurrentHostThreadID();
919 MicroProfileLeave(impl->microprofile_dynarmic[core], impl->dynarmic_ticks[core]); 937 MicroProfileLeave(impl->microprofile_cpu[core], impl->dynarmic_ticks[core]);
920} 938}
921 939
922bool System::IsMulticore() const { 940bool System::IsMulticore() const {
diff --git a/src/core/core.h b/src/core/core.h
index 4f153154f..93afc9303 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -189,6 +189,9 @@ public:
189 std::unique_lock<std::mutex> StallApplication(); 189 std::unique_lock<std::mutex> StallApplication();
190 void UnstallApplication(); 190 void UnstallApplication();
191 191
192 void SetNVDECActive(bool is_nvdec_active);
193 [[nodiscard]] bool GetNVDECActive();
194
192 /** 195 /**
193 * Initialize the debugger. 196 * Initialize the debugger.
194 */ 197 */
@@ -409,11 +412,11 @@ public:
409 /// Register a host thread as an auxiliary thread. 412 /// Register a host thread as an auxiliary thread.
410 void RegisterHostThread(); 413 void RegisterHostThread();
411 414
412 /// Enter Dynarmic Microprofile 415 /// Enter CPU Microprofile
413 void EnterDynarmicProfile(); 416 void EnterCPUProfile();
414 417
415 /// Exit Dynarmic Microprofile 418 /// Exit CPU Microprofile
416 void ExitDynarmicProfile(); 419 void ExitCPUProfile();
417 420
418 /// Tells if system is running on multicore. 421 /// Tells if system is running on multicore.
419 [[nodiscard]] bool IsMulticore() const; 422 [[nodiscard]] bool IsMulticore() const;
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 65a9fe802..4ff2c50e5 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -569,6 +569,10 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
569} 569}
570 570
571KeyManager::KeyManager() { 571KeyManager::KeyManager() {
572 ReloadKeys();
573}
574
575void KeyManager::ReloadKeys() {
572 // Initialize keys 576 // Initialize keys
573 const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); 577 const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
574 578
@@ -702,6 +706,10 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
702 } 706 }
703} 707}
704 708
709bool KeyManager::AreKeysLoaded() const {
710 return !s128_keys.empty() && !s256_keys.empty();
711}
712
705bool KeyManager::BaseDeriveNecessary() const { 713bool KeyManager::BaseDeriveNecessary() const {
706 const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) { 714 const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) {
707 return !HasKey(key_type, index1, index2); 715 return !HasKey(key_type, index1, index2);
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 673cec463..8c864503b 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -267,6 +267,9 @@ public:
267 bool AddTicketCommon(Ticket raw); 267 bool AddTicketCommon(Ticket raw);
268 bool AddTicketPersonalized(Ticket raw); 268 bool AddTicketPersonalized(Ticket raw);
269 269
270 void ReloadKeys();
271 bool AreKeysLoaded() const;
272
270private: 273private:
271 KeyManager(); 274 KeyManager();
272 275
diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp
index f8b5be2b4..de3f8ef8f 100644
--- a/src/core/device_memory.cpp
+++ b/src/core/device_memory.cpp
@@ -6,9 +6,15 @@
6 6
7namespace Core { 7namespace Core {
8 8
9#ifdef ANDROID
10constexpr size_t VirtualReserveSize = 1ULL << 38;
11#else
12constexpr size_t VirtualReserveSize = 1ULL << 39;
13#endif
14
9DeviceMemory::DeviceMemory() 15DeviceMemory::DeviceMemory()
10 : buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(), 16 : buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
11 1ULL << 39} {} 17 VirtualReserveSize} {}
12DeviceMemory::~DeviceMemory() = default; 18DeviceMemory::~DeviceMemory() = default;
13 19
14} // namespace Core 20} // namespace Core
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 50f44f598..cd9ac2e75 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -23,8 +23,8 @@ const std::array<const char*, 16> LANGUAGE_NAMES{{
23 "Portuguese", 23 "Portuguese",
24 "Russian", 24 "Russian",
25 "Korean", 25 "Korean",
26 "Taiwanese", 26 "TraditionalChinese",
27 "Chinese", 27 "SimplifiedChinese",
28 "BrazilianPortuguese", 28 "BrazilianPortuguese",
29}}; 29}};
30 30
@@ -45,17 +45,17 @@ constexpr std::array<Language, 18> language_to_codes = {{
45 Language::German, 45 Language::German,
46 Language::Italian, 46 Language::Italian,
47 Language::Spanish, 47 Language::Spanish,
48 Language::Chinese, 48 Language::SimplifiedChinese,
49 Language::Korean, 49 Language::Korean,
50 Language::Dutch, 50 Language::Dutch,
51 Language::Portuguese, 51 Language::Portuguese,
52 Language::Russian, 52 Language::Russian,
53 Language::Taiwanese, 53 Language::TraditionalChinese,
54 Language::BritishEnglish, 54 Language::BritishEnglish,
55 Language::CanadianFrench, 55 Language::CanadianFrench,
56 Language::LatinAmericanSpanish, 56 Language::LatinAmericanSpanish,
57 Language::Chinese, 57 Language::SimplifiedChinese,
58 Language::Taiwanese, 58 Language::TraditionalChinese,
59 Language::BrazilianPortuguese, 59 Language::BrazilianPortuguese,
60}}; 60}};
61 61
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 6a81873b1..c98efb00d 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -84,8 +84,8 @@ enum class Language : u8 {
84 Portuguese = 10, 84 Portuguese = 10,
85 Russian = 11, 85 Russian = 11,
86 Korean = 12, 86 Korean = 12,
87 Taiwanese = 13, 87 TraditionalChinese = 13,
88 Chinese = 14, 88 SimplifiedChinese = 14,
89 BrazilianPortuguese = 15, 89 BrazilianPortuguese = 15,
90 90
91 Default = 255, 91 Default = 255,
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index f786f2add..4e61d4335 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -25,6 +25,8 @@
25#include "core/file_sys/vfs_layered.h" 25#include "core/file_sys/vfs_layered.h"
26#include "core/file_sys/vfs_vector.h" 26#include "core/file_sys/vfs_vector.h"
27#include "core/hle/service/filesystem/filesystem.h" 27#include "core/hle/service/filesystem/filesystem.h"
28#include "core/hle/service/ns/language.h"
29#include "core/hle/service/set/set.h"
28#include "core/loader/loader.h" 30#include "core/loader/loader.h"
29#include "core/loader/nso.h" 31#include "core/loader/nso.h"
30#include "core/memory/cheat_engine.h" 32#include "core/memory/cheat_engine.h"
@@ -624,8 +626,37 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
624 626
625 auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file); 627 auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
626 628
629 // Get language code from settings
630 const auto language_code =
631 Service::Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue());
632
633 // Convert to application language and get priority list
634 const auto application_language =
635 Service::NS::ConvertToApplicationLanguage(language_code)
636 .value_or(Service::NS::ApplicationLanguage::AmericanEnglish);
637 const auto language_priority_list =
638 Service::NS::GetApplicationLanguagePriorityList(application_language);
639
640 // Convert to language names
641 auto priority_language_names = FileSys::LANGUAGE_NAMES; // Copy
642 if (language_priority_list) {
643 for (size_t i = 0; i < priority_language_names.size(); ++i) {
644 // Relies on FileSys::LANGUAGE_NAMES being in the same order as
645 // Service::NS::ApplicationLanguage
646 const auto language_index = static_cast<u8>(language_priority_list->at(i));
647
648 if (language_index < FileSys::LANGUAGE_NAMES.size()) {
649 priority_language_names[i] = FileSys::LANGUAGE_NAMES[language_index];
650 } else {
651 // Not a catastrophe, unlikely to happen
652 LOG_WARNING(Loader, "Invalid language index {}", language_index);
653 }
654 }
655 }
656
657 // Get first matching icon
627 VirtualFile icon_file; 658 VirtualFile icon_file;
628 for (const auto& language : FileSys::LANGUAGE_NAMES) { 659 for (const auto& language : priority_language_names) {
629 icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat")); 660 icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat"));
630 if (icon_file != nullptr) { 661 if (icon_file != nullptr) {
631 break; 662 break;
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index 3226b884a..27f97c725 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -8,6 +8,7 @@
8#include <set> 8#include <set>
9#include <vector> 9#include <vector>
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/file_sys/nca_metadata.h"
11#include "core/file_sys/vfs.h" 12#include "core/file_sys/vfs.h"
12 13
13namespace Core::Crypto { 14namespace Core::Crypto {
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 1be2dccb0..d1f1ca8c9 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -6,8 +6,6 @@
6 6
7namespace Core::Frontend { 7namespace Core::Frontend {
8 8
9GraphicsContext::~GraphicsContext() = default;
10
11EmuWindow::EmuWindow() { 9EmuWindow::EmuWindow() {
12 // TODO: Find a better place to set this. 10 // TODO: Find a better place to set this.
13 config.min_client_area_size = 11 config.min_client_area_size =
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 1093800f6..a72df034e 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -5,11 +5,14 @@
5 5
6#include <memory> 6#include <memory>
7#include <utility> 7#include <utility>
8
8#include "common/common_types.h" 9#include "common/common_types.h"
9#include "core/frontend/framebuffer_layout.h" 10#include "core/frontend/framebuffer_layout.h"
10 11
11namespace Core::Frontend { 12namespace Core::Frontend {
12 13
14class GraphicsContext;
15
13/// Information for the Graphics Backends signifying what type of screen pointer is in 16/// Information for the Graphics Backends signifying what type of screen pointer is in
14/// WindowInformation 17/// WindowInformation
15enum class WindowSystemType { 18enum class WindowSystemType {
@@ -22,51 +25,6 @@ enum class WindowSystemType {
22}; 25};
23 26
24/** 27/**
25 * Represents a drawing context that supports graphics operations.
26 */
27class GraphicsContext {
28public:
29 virtual ~GraphicsContext();
30
31 /// Inform the driver to swap the front/back buffers and present the current image
32 virtual void SwapBuffers() {}
33
34 /// Makes the graphics context current for the caller thread
35 virtual void MakeCurrent() {}
36
37 /// Releases (dunno if this is the "right" word) the context from the caller thread
38 virtual void DoneCurrent() {}
39
40 class Scoped {
41 public:
42 [[nodiscard]] explicit Scoped(GraphicsContext& context_) : context(context_) {
43 context.MakeCurrent();
44 }
45 ~Scoped() {
46 if (active) {
47 context.DoneCurrent();
48 }
49 }
50
51 /// In the event that context was destroyed before the Scoped is destroyed, this provides a
52 /// mechanism to prevent calling a destroyed object's method during the deconstructor
53 void Cancel() {
54 active = false;
55 }
56
57 private:
58 GraphicsContext& context;
59 bool active{true};
60 };
61
62 /// Calls MakeCurrent on the context and calls DoneCurrent when the scope for the returned value
63 /// ends
64 [[nodiscard]] Scoped Acquire() {
65 return Scoped{*this};
66 }
67};
68
69/**
70 * Abstraction class used to provide an interface between emulation code and the frontend 28 * Abstraction class used to provide an interface between emulation code and the frontend
71 * (e.g. SDL, QGLWidget, GLFW, etc...). 29 * (e.g. SDL, QGLWidget, GLFW, etc...).
72 * 30 *
diff --git a/src/core/frontend/graphics_context.h b/src/core/frontend/graphics_context.h
new file mode 100644
index 000000000..7554c1583
--- /dev/null
+++ b/src/core/frontend/graphics_context.h
@@ -0,0 +1,62 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7
8#include "common/dynamic_library.h"
9
10namespace Core::Frontend {
11
12/**
13 * Represents a drawing context that supports graphics operations.
14 */
15class GraphicsContext {
16public:
17 virtual ~GraphicsContext() = default;
18
19 /// Inform the driver to swap the front/back buffers and present the current image
20 virtual void SwapBuffers() {}
21
22 /// Makes the graphics context current for the caller thread
23 virtual void MakeCurrent() {}
24
25 /// Releases (dunno if this is the "right" word) the context from the caller thread
26 virtual void DoneCurrent() {}
27
28 /// Gets the GPU driver library (used by Android only)
29 virtual std::shared_ptr<Common::DynamicLibrary> GetDriverLibrary() {
30 return {};
31 }
32
33 class Scoped {
34 public:
35 [[nodiscard]] explicit Scoped(GraphicsContext& context_) : context(context_) {
36 context.MakeCurrent();
37 }
38 ~Scoped() {
39 if (active) {
40 context.DoneCurrent();
41 }
42 }
43
44 /// In the event that context was destroyed before the Scoped is destroyed, this provides a
45 /// mechanism to prevent calling a destroyed object's method during the deconstructor
46 void Cancel() {
47 active = false;
48 }
49
50 private:
51 GraphicsContext& context;
52 bool active{true};
53 };
54
55 /// Calls MakeCurrent on the context and calls DoneCurrent when the scope for the returned value
56 /// ends
57 [[nodiscard]] Scoped Acquire() {
58 return Scoped{*this};
59 }
60};
61
62} // namespace Core::Frontend
diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp
index 17d663379..b4afd930e 100644
--- a/src/core/hid/emulated_console.cpp
+++ b/src/core/hid/emulated_console.cpp
@@ -13,7 +13,7 @@ EmulatedConsole::~EmulatedConsole() = default;
13void EmulatedConsole::ReloadFromSettings() { 13void EmulatedConsole::ReloadFromSettings() {
14 // Using first motion device from player 1. No need to assign any unique config at the moment 14 // Using first motion device from player 1. No need to assign any unique config at the moment
15 const auto& player = Settings::values.players.GetValue()[0]; 15 const auto& player = Settings::values.players.GetValue()[0];
16 motion_params = Common::ParamPackage(player.motions[0]); 16 motion_params[0] = Common::ParamPackage(player.motions[0]);
17 17
18 ReloadInput(); 18 ReloadInput();
19} 19}
@@ -74,14 +74,30 @@ void EmulatedConsole::ReloadInput() {
74 // If you load any device here add the equivalent to the UnloadInput() function 74 // If you load any device here add the equivalent to the UnloadInput() function
75 SetTouchParams(); 75 SetTouchParams();
76 76
77 motion_devices = Common::Input::CreateInputDevice(motion_params); 77 motion_params[1] = Common::ParamPackage{"engine:virtual_gamepad,port:8,motion:0"};
78 if (motion_devices) { 78
79 motion_devices->SetCallback({ 79 for (std::size_t index = 0; index < motion_devices.size(); ++index) {
80 motion_devices[index] = Common::Input::CreateInputDevice(motion_params[index]);
81 if (!motion_devices[index]) {
82 continue;
83 }
84 motion_devices[index]->SetCallback({
80 .on_change = 85 .on_change =
81 [this](const Common::Input::CallbackStatus& callback) { SetMotion(callback); }, 86 [this](const Common::Input::CallbackStatus& callback) { SetMotion(callback); },
82 }); 87 });
83 } 88 }
84 89
90 // Restore motion state
91 auto& emulated_motion = console.motion_values.emulated;
92 auto& motion = console.motion_state;
93 emulated_motion.ResetRotations();
94 emulated_motion.ResetQuaternion();
95 motion.accel = emulated_motion.GetAcceleration();
96 motion.gyro = emulated_motion.GetGyroscope();
97 motion.rotation = emulated_motion.GetRotations();
98 motion.orientation = emulated_motion.GetOrientation();
99 motion.is_at_rest = !emulated_motion.IsMoving(motion_sensitivity);
100
85 // Unique index for identifying touch device source 101 // Unique index for identifying touch device source
86 std::size_t index = 0; 102 std::size_t index = 0;
87 for (auto& touch_device : touch_devices) { 103 for (auto& touch_device : touch_devices) {
@@ -100,7 +116,9 @@ void EmulatedConsole::ReloadInput() {
100} 116}
101 117
102void EmulatedConsole::UnloadInput() { 118void EmulatedConsole::UnloadInput() {
103 motion_devices.reset(); 119 for (auto& motion : motion_devices) {
120 motion.reset();
121 }
104 for (auto& touch : touch_devices) { 122 for (auto& touch : touch_devices) {
105 touch.reset(); 123 touch.reset();
106 } 124 }
@@ -133,11 +151,11 @@ void EmulatedConsole::RestoreConfig() {
133} 151}
134 152
135Common::ParamPackage EmulatedConsole::GetMotionParam() const { 153Common::ParamPackage EmulatedConsole::GetMotionParam() const {
136 return motion_params; 154 return motion_params[0];
137} 155}
138 156
139void EmulatedConsole::SetMotionParam(Common::ParamPackage param) { 157void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
140 motion_params = std::move(param); 158 motion_params[0] = std::move(param);
141 ReloadInput(); 159 ReloadInput();
142} 160}
143 161
diff --git a/src/core/hid/emulated_console.h b/src/core/hid/emulated_console.h
index 697ecd2d6..79114bb6d 100644
--- a/src/core/hid/emulated_console.h
+++ b/src/core/hid/emulated_console.h
@@ -29,10 +29,10 @@ struct ConsoleMotionInfo {
29 MotionInput emulated{}; 29 MotionInput emulated{};
30}; 30};
31 31
32using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>; 32using ConsoleMotionDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 2>;
33using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, MaxTouchDevices>; 33using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, MaxTouchDevices>;
34 34
35using ConsoleMotionParams = Common::ParamPackage; 35using ConsoleMotionParams = std::array<Common::ParamPackage, 2>;
36using TouchParams = std::array<Common::ParamPackage, MaxTouchDevices>; 36using TouchParams = std::array<Common::ParamPackage, MaxTouchDevices>;
37 37
38using ConsoleMotionValues = ConsoleMotionInfo; 38using ConsoleMotionValues = ConsoleMotionInfo;
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index bbfea7117..0a7777732 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -193,6 +193,8 @@ void EmulatedController::LoadDevices() {
193 Common::Input::CreateInputDevice); 193 Common::Input::CreateInputDevice);
194 std::ranges::transform(virtual_stick_params, virtual_stick_devices.begin(), 194 std::ranges::transform(virtual_stick_params, virtual_stick_devices.begin(),
195 Common::Input::CreateInputDevice); 195 Common::Input::CreateInputDevice);
196 std::ranges::transform(virtual_motion_params, virtual_motion_devices.begin(),
197 Common::Input::CreateInputDevice);
196} 198}
197 199
198void EmulatedController::LoadTASParams() { 200void EmulatedController::LoadTASParams() {
@@ -253,6 +255,12 @@ void EmulatedController::LoadVirtualGamepadParams() {
253 for (auto& param : virtual_stick_params) { 255 for (auto& param : virtual_stick_params) {
254 param = common_params; 256 param = common_params;
255 } 257 }
258 for (auto& param : virtual_stick_params) {
259 param = common_params;
260 }
261 for (auto& param : virtual_motion_params) {
262 param = common_params;
263 }
256 264
257 // TODO(german77): Replace this with an input profile or something better 265 // TODO(german77): Replace this with an input profile or something better
258 virtual_button_params[Settings::NativeButton::A].Set("button", 0); 266 virtual_button_params[Settings::NativeButton::A].Set("button", 0);
@@ -284,6 +292,9 @@ void EmulatedController::LoadVirtualGamepadParams() {
284 virtual_stick_params[Settings::NativeAnalog::LStick].Set("range", 1.0f); 292 virtual_stick_params[Settings::NativeAnalog::LStick].Set("range", 1.0f);
285 virtual_stick_params[Settings::NativeAnalog::RStick].Set("deadzone", 0.0f); 293 virtual_stick_params[Settings::NativeAnalog::RStick].Set("deadzone", 0.0f);
286 virtual_stick_params[Settings::NativeAnalog::RStick].Set("range", 1.0f); 294 virtual_stick_params[Settings::NativeAnalog::RStick].Set("range", 1.0f);
295
296 virtual_motion_params[Settings::NativeMotion::MotionLeft].Set("motion", 0);
297 virtual_motion_params[Settings::NativeMotion::MotionRight].Set("motion", 0);
287} 298}
288 299
289void EmulatedController::ReloadInput() { 300void EmulatedController::ReloadInput() {
@@ -463,6 +474,18 @@ void EmulatedController::ReloadInput() {
463 }, 474 },
464 }); 475 });
465 } 476 }
477
478 for (std::size_t index = 0; index < virtual_motion_devices.size(); ++index) {
479 if (!virtual_motion_devices[index]) {
480 continue;
481 }
482 virtual_motion_devices[index]->SetCallback({
483 .on_change =
484 [this, index](const Common::Input::CallbackStatus& callback) {
485 SetMotion(callback, index);
486 },
487 });
488 }
466 turbo_button_state = 0; 489 turbo_button_state = 0;
467} 490}
468 491
@@ -500,6 +523,9 @@ void EmulatedController::UnloadInput() {
500 for (auto& stick : virtual_stick_devices) { 523 for (auto& stick : virtual_stick_devices) {
501 stick.reset(); 524 stick.reset();
502 } 525 }
526 for (auto& motion : virtual_motion_devices) {
527 motion.reset();
528 }
503 for (auto& camera : camera_devices) { 529 for (auto& camera : camera_devices) {
504 camera.reset(); 530 camera.reset();
505 } 531 }
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 88fad2f56..09fe1a0ab 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -568,8 +568,10 @@ private:
568 // Virtual gamepad related variables 568 // Virtual gamepad related variables
569 ButtonParams virtual_button_params; 569 ButtonParams virtual_button_params;
570 StickParams virtual_stick_params; 570 StickParams virtual_stick_params;
571 ControllerMotionParams virtual_motion_params;
571 ButtonDevices virtual_button_devices; 572 ButtonDevices virtual_button_devices;
572 StickDevices virtual_stick_devices; 573 StickDevices virtual_stick_devices;
574 ControllerMotionDevices virtual_motion_devices;
573 575
574 mutable std::mutex mutex; 576 mutable std::mutex mutex;
575 mutable std::mutex callback_mutex; 577 mutable std::mutex callback_mutex;
diff --git a/src/core/hle/kernel/k_address_space_info.cpp b/src/core/hle/kernel/k_address_space_info.cpp
index c36eb5dc4..32173e52b 100644
--- a/src/core/hle/kernel/k_address_space_info.cpp
+++ b/src/core/hle/kernel/k_address_space_info.cpp
@@ -25,7 +25,12 @@ constexpr std::array<KAddressSpaceInfo, 13> AddressSpaceInfos{{
25 { .bit_width = 36, .address = 2_GiB , .size = 64_GiB - 2_GiB , .type = KAddressSpaceInfo::Type::MapLarge, }, 25 { .bit_width = 36, .address = 2_GiB , .size = 64_GiB - 2_GiB , .type = KAddressSpaceInfo::Type::MapLarge, },
26 { .bit_width = 36, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, }, 26 { .bit_width = 36, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, },
27 { .bit_width = 36, .address = Size_Invalid, .size = 6_GiB , .type = KAddressSpaceInfo::Type::Alias, }, 27 { .bit_width = 36, .address = Size_Invalid, .size = 6_GiB , .type = KAddressSpaceInfo::Type::Alias, },
28#ifdef ANDROID
29 // With Android, we use a 38-bit address space due to memory limitations. This should (safely) truncate ASLR region.
30 { .bit_width = 39, .address = 128_MiB , .size = 256_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, },
31#else
28 { .bit_width = 39, .address = 128_MiB , .size = 512_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, }, 32 { .bit_width = 39, .address = 128_MiB , .size = 512_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, },
33#endif
29 { .bit_width = 39, .address = Size_Invalid, .size = 64_GiB , .type = KAddressSpaceInfo::Type::MapSmall }, 34 { .bit_width = 39, .address = Size_Invalid, .size = 64_GiB , .type = KAddressSpaceInfo::Type::MapSmall },
30 { .bit_width = 39, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, }, 35 { .bit_width = 39, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, },
31 { .bit_width = 39, .address = Size_Invalid, .size = 64_GiB , .type = KAddressSpaceInfo::Type::Alias, }, 36 { .bit_width = 39, .address = Size_Invalid, .size = 64_GiB , .type = KAddressSpaceInfo::Type::Alias, },
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 63fd5bfd6..5542d6cbc 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -46,6 +46,7 @@ ProfileManager::ProfileManager() {
46 // Create an user if none are present 46 // Create an user if none are present
47 if (user_count == 0) { 47 if (user_count == 0) {
48 CreateNewUser(UUID::MakeRandom(), "yuzu"); 48 CreateNewUser(UUID::MakeRandom(), "yuzu");
49 WriteUserSaveFile();
49 } 50 }
50 51
51 auto current = 52 auto current =
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 0bd7900e1..b14f682b5 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -12,6 +12,11 @@
12#pragma warning(pop) 12#pragma warning(pop)
13#endif 13#endif
14 14
15#include <fmt/format.h>
16
17#include "common/fs/file.h"
18#include "common/fs/fs.h"
19#include "common/fs/path_util.h"
15#include "common/input.h" 20#include "common/input.h"
16#include "common/logging/log.h" 21#include "common/logging/log.h"
17#include "common/string_util.h" 22#include "common/string_util.h"
@@ -136,7 +141,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
136 if (!NFP::AmiiboCrypto::IsKeyAvailable()) { 141 if (!NFP::AmiiboCrypto::IsKeyAvailable()) {
137 LOG_INFO(Service_NFC, "Loading amiibo without keys"); 142 LOG_INFO(Service_NFC, "Loading amiibo without keys");
138 memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); 143 memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
139 BuildAmiiboWithoutKeys(); 144 BuildAmiiboWithoutKeys(tag_data, encrypted_tag_data);
140 is_plain_amiibo = true; 145 is_plain_amiibo = true;
141 is_write_protected = true; 146 is_write_protected = true;
142 return true; 147 return true;
@@ -366,16 +371,25 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
366 371
367 // The loaded amiibo is not encrypted 372 // The loaded amiibo is not encrypted
368 if (is_plain_amiibo) { 373 if (is_plain_amiibo) {
374 std::vector<u8> data(sizeof(NFP::NTAG215File));
375 memcpy(data.data(), &tag_data, sizeof(tag_data));
376 WriteBackupData(tag_data.uid, data);
377
369 device_state = DeviceState::TagMounted; 378 device_state = DeviceState::TagMounted;
370 mount_target = mount_target_; 379 mount_target = mount_target_;
371 return ResultSuccess; 380 return ResultSuccess;
372 } 381 }
373 382
374 if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { 383 if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
375 LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state); 384 bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess();
376 return ResultCorruptedData; 385 LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
386 return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
377 } 387 }
378 388
389 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
390 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
391 WriteBackupData(encrypted_tag_data.uuid.uid, data);
392
379 device_state = DeviceState::TagMounted; 393 device_state = DeviceState::TagMounted;
380 mount_target = mount_target_; 394 mount_target = mount_target_;
381 return ResultSuccess; 395 return ResultSuccess;
@@ -470,6 +484,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
470 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); 484 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
471 if (is_plain_amiibo) { 485 if (is_plain_amiibo) {
472 memcpy(data.data(), &tag_data, sizeof(tag_data)); 486 memcpy(data.data(), &tag_data, sizeof(tag_data));
487 WriteBackupData(tag_data.uid, data);
473 } else { 488 } else {
474 if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { 489 if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
475 LOG_ERROR(Service_NFP, "Failed to encode data"); 490 LOG_ERROR(Service_NFP, "Failed to encode data");
@@ -477,6 +492,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
477 } 492 }
478 493
479 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); 494 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
495 WriteBackupData(encrypted_tag_data.uuid.uid, data);
480 } 496 }
481 497
482 if (!npad_device->WriteNfc(data)) { 498 if (!npad_device->WriteNfc(data)) {
@@ -488,7 +504,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
488} 504}
489 505
490Result NfcDevice::Restore() { 506Result NfcDevice::Restore() {
491 if (device_state != DeviceState::TagMounted) { 507 if (device_state != DeviceState::TagFound) {
492 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 508 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
493 if (device_state == DeviceState::TagRemoved) { 509 if (device_state == DeviceState::TagRemoved) {
494 return ResultTagRemoved; 510 return ResultTagRemoved;
@@ -496,13 +512,59 @@ Result NfcDevice::Restore() {
496 return ResultWrongDeviceState; 512 return ResultWrongDeviceState;
497 } 513 }
498 514
499 if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { 515 NFC::TagInfo tag_info{};
500 LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); 516 std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
501 return ResultWrongDeviceState; 517 Result result = GetTagInfo(tag_info, false);
518
519 if (result.IsError()) {
520 return result;
502 } 521 }
503 522
504 // TODO: Load amiibo from backup on system 523 result = ReadBackupData(tag_info.uuid, data);
505 LOG_ERROR(Service_NFP, "Not Implemented"); 524
525 if (result.IsError()) {
526 return result;
527 }
528
529 NFP::NTAG215File temporary_tag_data{};
530 NFP::EncryptedNTAG215File temporary_encrypted_tag_data{};
531
532 // Fallback for encrypted amiibos without keys
533 if (is_write_protected) {
534 return ResultWriteAmiiboFailed;
535 }
536
537 // Fallback for plain amiibos
538 if (is_plain_amiibo) {
539 LOG_INFO(Service_NFP, "Restoring backup of plain amiibo");
540 memcpy(&temporary_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
541 temporary_encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(temporary_tag_data);
542 }
543
544 if (!is_plain_amiibo) {
545 LOG_INFO(Service_NFP, "Restoring backup of encrypted amiibo");
546 temporary_tag_data = {};
547 memcpy(&temporary_encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
548 }
549
550 if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) {
551 return ResultNotAnAmiibo;
552 }
553
554 if (!is_plain_amiibo) {
555 if (!NFP::AmiiboCrypto::DecodeAmiibo(temporary_encrypted_tag_data, temporary_tag_data)) {
556 LOG_ERROR(Service_NFP, "Can't decode amiibo");
557 return ResultCorruptedData;
558 }
559 }
560
561 // Overwrite tag contents with backup and mount the tag
562 tag_data = temporary_tag_data;
563 encrypted_tag_data = temporary_encrypted_tag_data;
564 device_state = DeviceState::TagMounted;
565 mount_target = NFP::MountTarget::All;
566 is_data_moddified = true;
567
506 return ResultSuccess; 568 return ResultSuccess;
507} 569}
508 570
@@ -1132,13 +1194,69 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) {
1132 return FlushWithBreak(break_type); 1194 return FlushWithBreak(break_type);
1133} 1195}
1134 1196
1135Result NfcDevice::ReadBackupData(std::span<u8> data) const { 1197Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const {
1136 // Not implemented 1198 constexpr auto backup_dir = "backup";
1199 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
1200 const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
1201
1202 if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) {
1203 return ResultUnableToAccessBackupFile;
1204 }
1205
1137 return ResultSuccess; 1206 return ResultSuccess;
1138} 1207}
1139 1208
1140Result NfcDevice::WriteBackupData(std::span<const u8> data) { 1209Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const {
1141 // Not implemented 1210 constexpr auto backup_dir = "backup";
1211 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
1212 const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
1213
1214 const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
1215 Common::FS::FileAccessMode::Read,
1216 Common::FS::FileType::BinaryFile};
1217
1218 if (!keys_file.IsOpen()) {
1219 LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
1220 return ResultUnableToAccessBackupFile;
1221 }
1222
1223 if (keys_file.Read(data) != data.size()) {
1224 LOG_ERROR(Service_NFP, "Failed to read amiibo backup");
1225 return ResultUnableToAccessBackupFile;
1226 }
1227
1228 return ResultSuccess;
1229}
1230
1231Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) {
1232 constexpr auto backup_dir = "backup";
1233 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
1234 const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
1235
1236 if (HasBackup(uid).IsError()) {
1237 if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) {
1238 return ResultBackupPathAlreadyExist;
1239 }
1240
1241 if (!Common::FS::NewFile(yuzu_amiibo_dir / backup_dir / file_name)) {
1242 return ResultBackupPathAlreadyExist;
1243 }
1244 }
1245
1246 const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
1247 Common::FS::FileAccessMode::ReadWrite,
1248 Common::FS::FileType::BinaryFile};
1249
1250 if (!keys_file.IsOpen()) {
1251 LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
1252 return ResultUnableToAccessBackupFile;
1253 }
1254
1255 if (keys_file.Write(data) != data.size()) {
1256 LOG_ERROR(Service_NFP, "Failed to write amiibo backup");
1257 return ResultUnableToAccessBackupFile;
1258 }
1259
1142 return ResultSuccess; 1260 return ResultSuccess;
1143} 1261}
1144 1262
@@ -1177,7 +1295,8 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
1177 return amiibo_name; 1295 return amiibo_name;
1178} 1296}
1179 1297
1180void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) { 1298void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings,
1299 const NFP::AmiiboName& amiibo_name) const {
1181 std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{}; 1300 std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};
1182 1301
1183 // Convert from utf8 to utf16 1302 // Convert from utf8 to utf16
@@ -1258,22 +1377,23 @@ void NfcDevice::UpdateRegisterInfoCrc() {
1258 tag_data.register_info_crc = crc.checksum(); 1377 tag_data.register_info_crc = crc.checksum();
1259} 1378}
1260 1379
1261void NfcDevice::BuildAmiiboWithoutKeys() { 1380void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
1381 const NFP::EncryptedNTAG215File& encrypted_file) const {
1262 Service::Mii::MiiManager manager; 1382 Service::Mii::MiiManager manager;
1263 auto& settings = tag_data.settings; 1383 auto& settings = stubbed_tag_data.settings;
1264 1384
1265 tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_tag_data); 1385 stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
1266 1386
1267 // Common info 1387 // Common info
1268 tag_data.write_counter = 0; 1388 stubbed_tag_data.write_counter = 0;
1269 tag_data.amiibo_version = 0; 1389 stubbed_tag_data.amiibo_version = 0;
1270 settings.write_date = GetAmiiboDate(GetCurrentPosixTime()); 1390 settings.write_date = GetAmiiboDate(GetCurrentPosixTime());
1271 1391
1272 // Register info 1392 // Register info
1273 SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); 1393 SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
1274 settings.settings.font_region.Assign(0); 1394 settings.settings.font_region.Assign(0);
1275 settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); 1395 settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
1276 tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); 1396 stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
1277 1397
1278 // Admin info 1398 // Admin info
1279 settings.settings.amiibo_initialized.Assign(1); 1399 settings.settings.amiibo_initialized.Assign(1);
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 6a37e8458..6f049b687 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -86,8 +86,9 @@ public:
86 Result GetAll(NFP::NfpData& data) const; 86 Result GetAll(NFP::NfpData& data) const;
87 Result SetAll(const NFP::NfpData& data); 87 Result SetAll(const NFP::NfpData& data);
88 Result BreakTag(NFP::BreakType break_type); 88 Result BreakTag(NFP::BreakType break_type);
89 Result ReadBackupData(std::span<u8> data) const; 89 Result HasBackup(const NFC::UniqueSerialNumber& uid) const;
90 Result WriteBackupData(std::span<const u8> data); 90 Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const;
91 Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data);
91 Result WriteNtf(std::span<const u8> data); 92 Result WriteNtf(std::span<const u8> data);
92 93
93 u64 GetHandle() const; 94 u64 GetHandle() const;
@@ -103,14 +104,15 @@ private:
103 void CloseNfcTag(); 104 void CloseNfcTag();
104 105
105 NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const; 106 NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
106 void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name); 107 void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) const;
107 NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const; 108 NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const;
108 u64 GetCurrentPosixTime() const; 109 u64 GetCurrentPosixTime() const;
109 u64 RemoveVersionByte(u64 application_id) const; 110 u64 RemoveVersionByte(u64 application_id) const;
110 void UpdateSettingsCrc(); 111 void UpdateSettingsCrc();
111 void UpdateRegisterInfoCrc(); 112 void UpdateRegisterInfoCrc();
112 113
113 void BuildAmiiboWithoutKeys(); 114 void BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
115 const NFP::EncryptedNTAG215File& encrypted_file) const;
114 116
115 bool is_controller_set{}; 117 bool is_controller_set{};
116 int callback_key; 118 int callback_key;
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp
index d5deaaf27..cffd602df 100644
--- a/src/core/hle/service/nfc/common/device_manager.cpp
+++ b/src/core/hle/service/nfc/common/device_manager.cpp
@@ -543,9 +543,14 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
543 543
544 std::shared_ptr<NfcDevice> device = nullptr; 544 std::shared_ptr<NfcDevice> device = nullptr;
545 auto result = GetDeviceHandle(device_handle, device); 545 auto result = GetDeviceHandle(device_handle, device);
546 NFC::TagInfo tag_info{};
546 547
547 if (result.IsSuccess()) { 548 if (result.IsSuccess()) {
548 result = device->ReadBackupData(data); 549 result = device->GetTagInfo(tag_info, false);
550 }
551
552 if (result.IsSuccess()) {
553 result = device->ReadBackupData(tag_info.uuid, data);
549 result = VerifyDeviceResult(device, result); 554 result = VerifyDeviceResult(device, result);
550 } 555 }
551 556
@@ -557,9 +562,14 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
557 562
558 std::shared_ptr<NfcDevice> device = nullptr; 563 std::shared_ptr<NfcDevice> device = nullptr;
559 auto result = GetDeviceHandle(device_handle, device); 564 auto result = GetDeviceHandle(device_handle, device);
565 NFC::TagInfo tag_info{};
566
567 if (result.IsSuccess()) {
568 result = device->GetTagInfo(tag_info, false);
569 }
560 570
561 if (result.IsSuccess()) { 571 if (result.IsSuccess()) {
562 result = device->WriteBackupData(data); 572 result = device->WriteBackupData(tag_info.uuid, data);
563 result = VerifyDeviceResult(device, result); 573 result = VerifyDeviceResult(device, result);
564 } 574 }
565 575
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp
index 0fa29d398..198d0f2b9 100644
--- a/src/core/hle/service/nfc/nfc_interface.cpp
+++ b/src/core/hle/service/nfc/nfc_interface.cpp
@@ -302,7 +302,7 @@ Result NfcInterface::TranslateResultToServiceError(Result result) const {
302 return TranslateResultToNfp(result); 302 return TranslateResultToNfp(result);
303 } 303 }
304 default: 304 default:
305 if (result != ResultUnknown216) { 305 if (result != ResultBackupPathAlreadyExist) {
306 return result; 306 return result;
307 } 307 }
308 return ResultUnknown74; 308 return ResultUnknown74;
@@ -343,6 +343,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
343 if (result == ResultApplicationAreaIsNotInitialized) { 343 if (result == ResultApplicationAreaIsNotInitialized) {
344 return NFP::ResultApplicationAreaIsNotInitialized; 344 return NFP::ResultApplicationAreaIsNotInitialized;
345 } 345 }
346 if (result == ResultCorruptedDataWithBackup) {
347 return NFP::ResultCorruptedDataWithBackup;
348 }
346 if (result == ResultCorruptedData) { 349 if (result == ResultCorruptedData) {
347 return NFP::ResultCorruptedData; 350 return NFP::ResultCorruptedData;
348 } 351 }
@@ -355,6 +358,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
355 if (result == ResultNotAnAmiibo) { 358 if (result == ResultNotAnAmiibo) {
356 return NFP::ResultNotAnAmiibo; 359 return NFP::ResultNotAnAmiibo;
357 } 360 }
361 if (result == ResultUnableToAccessBackupFile) {
362 return NFP::ResultUnableToAccessBackupFile;
363 }
358 LOG_WARNING(Service_NFC, "Result conversion not handled"); 364 LOG_WARNING(Service_NFC, "Result conversion not handled");
359 return result; 365 return result;
360} 366}
diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h
index 917d79ef8..59a808740 100644
--- a/src/core/hle/service/nfc/nfc_result.h
+++ b/src/core/hle/service/nfc/nfc_result.h
@@ -9,20 +9,22 @@ namespace Service::NFC {
9 9
10constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64); 10constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64);
11constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65); 11constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65);
12constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68); 12constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFC, 68);
13constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73); 13constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73);
14constexpr Result ResultUnknown74(ErrorModule::NFC, 74); 14constexpr Result ResultUnknown74(ErrorModule::NFC, 74);
15constexpr Result ResultUnknown76(ErrorModule::NFC, 76); 15constexpr Result ResultUnknown76(ErrorModule::NFC, 76);
16constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77); 16constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77);
17constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80); 17constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80);
18constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88); 18constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFC, 88);
19constexpr Result ResultTagRemoved(ErrorModule::NFC, 97); 19constexpr Result ResultTagRemoved(ErrorModule::NFC, 97);
20constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120); 20constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFC, 113);
21constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); 21constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFC, 120);
22constexpr Result ResultCorruptedData(ErrorModule::NFP, 144); 22constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFC, 128);
23constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152); 23constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136);
24constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168); 24constexpr Result ResultCorruptedData(ErrorModule::NFC, 144);
25constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178); 25constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152);
26constexpr Result ResultUnknown216(ErrorModule::NFC, 216); 26constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168);
27constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178);
28constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216);
27 29
28} // namespace Service::NFC 30} // namespace Service::NFC
diff --git a/src/core/hle/service/nfp/nfp_interface.cpp b/src/core/hle/service/nfp/nfp_interface.cpp
index 21d159154..34ef9d82d 100644
--- a/src/core/hle/service/nfp/nfp_interface.cpp
+++ b/src/core/hle/service/nfp/nfp_interface.cpp
@@ -126,7 +126,7 @@ void Interface::Flush(HLERequestContext& ctx) {
126void Interface::Restore(HLERequestContext& ctx) { 126void Interface::Restore(HLERequestContext& ctx) {
127 IPC::RequestParser rp{ctx}; 127 IPC::RequestParser rp{ctx};
128 const auto device_handle{rp.Pop<u64>()}; 128 const auto device_handle{rp.Pop<u64>()};
129 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); 129 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
130 130
131 auto result = GetManager()->Restore(device_handle); 131 auto result = GetManager()->Restore(device_handle);
132 result = TranslateResultToServiceError(result); 132 result = TranslateResultToServiceError(result);
@@ -394,7 +394,7 @@ void Interface::BreakTag(HLERequestContext& ctx) {
394void Interface::ReadBackupData(HLERequestContext& ctx) { 394void Interface::ReadBackupData(HLERequestContext& ctx) {
395 IPC::RequestParser rp{ctx}; 395 IPC::RequestParser rp{ctx};
396 const auto device_handle{rp.Pop<u64>()}; 396 const auto device_handle{rp.Pop<u64>()};
397 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); 397 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
398 398
399 std::vector<u8> backup_data{}; 399 std::vector<u8> backup_data{};
400 auto result = GetManager()->ReadBackupData(device_handle, backup_data); 400 auto result = GetManager()->ReadBackupData(device_handle, backup_data);
@@ -412,7 +412,7 @@ void Interface::WriteBackupData(HLERequestContext& ctx) {
412 IPC::RequestParser rp{ctx}; 412 IPC::RequestParser rp{ctx};
413 const auto device_handle{rp.Pop<u64>()}; 413 const auto device_handle{rp.Pop<u64>()};
414 const auto backup_data_buffer{ctx.ReadBuffer()}; 414 const auto backup_data_buffer{ctx.ReadBuffer()};
415 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); 415 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
416 416
417 auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer); 417 auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer);
418 result = TranslateResultToServiceError(result); 418 result = TranslateResultToServiceError(result);
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
index 4c126cd81..618533843 100644
--- a/src/core/hle/service/nfp/nfp_result.h
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -17,9 +17,11 @@ constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
17constexpr Result ResultTagRemoved(ErrorModule::NFP, 97); 17constexpr Result ResultTagRemoved(ErrorModule::NFP, 97);
18constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120); 18constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
19constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); 19constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
20constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFP, 136);
20constexpr Result ResultCorruptedData(ErrorModule::NFP, 144); 21constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
21constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152); 22constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
22constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168); 23constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
23constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178); 24constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
25constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFP, 200);
24 26
25} // namespace Service::NFP 27} // namespace Service::NFP
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index 0c7aee1b8..dc45169ad 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -69,7 +69,7 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
69 69
70void nvhost_nvdec::OnOpen(DeviceFD fd) { 70void nvhost_nvdec::OnOpen(DeviceFD fd) {
71 LOG_INFO(Service_NVDRV, "NVDEC video stream started"); 71 LOG_INFO(Service_NVDRV, "NVDEC video stream started");
72 system.AudioCore().SetNVDECActive(true); 72 system.SetNVDECActive(true);
73} 73}
74 74
75void nvhost_nvdec::OnClose(DeviceFD fd) { 75void nvhost_nvdec::OnClose(DeviceFD fd) {
@@ -79,7 +79,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) {
79 if (iter != host1x_file.fd_to_id.end()) { 79 if (iter != host1x_file.fd_to_id.end()) {
80 system.GPU().ClearCdmaInstance(iter->second); 80 system.GPU().ClearCdmaInstance(iter->second);
81 } 81 }
82 system.AudioCore().SetNVDECActive(false); 82 system.SetNVDECActive(false);
83} 83}
84 84
85} // namespace Service::Nvidia::Devices 85} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 4988e6e17..da2d5890f 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -324,6 +324,10 @@ s64 Nvnflinger::GetNextTicks() const {
324 speed_scale = 0.01f; 324 speed_scale = 0.01f;
325 } 325 }
326 } 326 }
327 if (system.GetNVDECActive() && settings.use_video_framerate.GetValue()) {
328 // Run at intended presentation rate during video playback.
329 speed_scale = 1.f;
330 }
327 331
328 // As an extension, treat nonpositive swap interval as framerate multiplier. 332 // As an extension, treat nonpositive swap interval as framerate multiplier.
329 const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval) 333 const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval)
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 73d04d7ee..7be6cf5f3 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -33,7 +33,8 @@ static_assert(sizeof(NroSegmentHeader) == 0x8, "NroSegmentHeader has incorrect s
33struct NroHeader { 33struct NroHeader {
34 INSERT_PADDING_BYTES(0x4); 34 INSERT_PADDING_BYTES(0x4);
35 u32_le module_header_offset; 35 u32_le module_header_offset;
36 INSERT_PADDING_BYTES(0x8); 36 u32 magic_ext1;
37 u32 magic_ext2;
37 u32_le magic; 38 u32_le magic;
38 INSERT_PADDING_BYTES(0x4); 39 INSERT_PADDING_BYTES(0x4);
39 u32_le file_size; 40 u32_le file_size;
@@ -124,6 +125,16 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) {
124 return FileType::Error; 125 return FileType::Error;
125} 126}
126 127
128bool AppLoader_NRO::IsHomebrew() {
129 // Read NSO header
130 NroHeader nro_header{};
131 if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
132 return false;
133 }
134 return nro_header.magic_ext1 == Common::MakeMagic('H', 'O', 'M', 'E') &&
135 nro_header.magic_ext2 == Common::MakeMagic('B', 'R', 'E', 'W');
136}
137
127static constexpr u32 PageAlignSize(u32 size) { 138static constexpr u32 PageAlignSize(u32 size) {
128 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK); 139 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
129} 140}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index ccb77b581..8de6eebc6 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -38,6 +38,8 @@ public:
38 */ 38 */
39 static FileType IdentifyType(const FileSys::VirtualFile& nro_file); 39 static FileType IdentifyType(const FileSys::VirtualFile& nro_file);
40 40
41 bool IsHomebrew();
42
41 FileType GetFileType() const override { 43 FileType GetFileType() const override {
42 return IdentifyType(file); 44 return IdentifyType(file);
43 } 45 }
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
index 304f4c70b..f8bafe553 100644
--- a/src/input_common/drivers/virtual_amiibo.cpp
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -73,10 +73,7 @@ VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const {
73VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) { 73VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
74 const Common::FS::IOFile nfc_file{filename, Common::FS::FileAccessMode::Read, 74 const Common::FS::IOFile nfc_file{filename, Common::FS::FileAccessMode::Read,
75 Common::FS::FileType::BinaryFile}; 75 Common::FS::FileType::BinaryFile};
76 76 std::vector<u8> data{};
77 if (state != State::WaitingForAmiibo) {
78 return Info::WrongDeviceState;
79 }
80 77
81 if (!nfc_file.IsOpen()) { 78 if (!nfc_file.IsOpen()) {
82 return Info::UnableToLoad; 79 return Info::UnableToLoad;
@@ -85,14 +82,14 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
85 switch (nfc_file.GetSize()) { 82 switch (nfc_file.GetSize()) {
86 case AmiiboSize: 83 case AmiiboSize:
87 case AmiiboSizeWithoutPassword: 84 case AmiiboSizeWithoutPassword:
88 nfc_data.resize(AmiiboSize); 85 data.resize(AmiiboSize);
89 if (nfc_file.Read(nfc_data) < AmiiboSizeWithoutPassword) { 86 if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) {
90 return Info::NotAnAmiibo; 87 return Info::NotAnAmiibo;
91 } 88 }
92 break; 89 break;
93 case MifareSize: 90 case MifareSize:
94 nfc_data.resize(MifareSize); 91 data.resize(MifareSize);
95 if (nfc_file.Read(nfc_data) < MifareSize) { 92 if (nfc_file.Read(data) < MifareSize) {
96 return Info::NotAnAmiibo; 93 return Info::NotAnAmiibo;
97 } 94 }
98 break; 95 break;
@@ -101,7 +98,28 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
101 } 98 }
102 99
103 file_path = filename; 100 file_path = filename;
101 return LoadAmiibo(data);
102}
103
104VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) {
105 if (state != State::WaitingForAmiibo) {
106 return Info::WrongDeviceState;
107 }
108
109 switch (data.size_bytes()) {
110 case AmiiboSize:
111 case AmiiboSizeWithoutPassword:
112 nfc_data.resize(AmiiboSize);
113 break;
114 case MifareSize:
115 nfc_data.resize(MifareSize);
116 break;
117 default:
118 return Info::NotAnAmiibo;
119 }
120
104 state = State::AmiiboIsOpen; 121 state = State::AmiiboIsOpen;
122 memcpy(nfc_data.data(), data.data(), data.size_bytes());
105 SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data}); 123 SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data});
106 return Info::Success; 124 return Info::Success;
107} 125}
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
index 488d00b31..34e97cd91 100644
--- a/src/input_common/drivers/virtual_amiibo.h
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -4,6 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include <array> 6#include <array>
7#include <span>
7#include <string> 8#include <string>
8#include <vector> 9#include <vector>
9 10
@@ -47,6 +48,7 @@ public:
47 State GetCurrentState() const; 48 State GetCurrentState() const;
48 49
49 Info LoadAmiibo(const std::string& amiibo_file); 50 Info LoadAmiibo(const std::string& amiibo_file);
51 Info LoadAmiibo(std::span<u8> data);
50 Info ReloadAmiibo(); 52 Info ReloadAmiibo();
51 Info CloseAmiibo(); 53 Info CloseAmiibo();
52 54
diff --git a/src/input_common/drivers/virtual_gamepad.cpp b/src/input_common/drivers/virtual_gamepad.cpp
index 7db945aa6..c15cbbe58 100644
--- a/src/input_common/drivers/virtual_gamepad.cpp
+++ b/src/input_common/drivers/virtual_gamepad.cpp
@@ -39,6 +39,22 @@ void VirtualGamepad::SetStickPosition(std::size_t player_index, VirtualStick axi
39 SetStickPosition(player_index, static_cast<int>(axis_id), x_value, y_value); 39 SetStickPosition(player_index, static_cast<int>(axis_id), x_value, y_value);
40} 40}
41 41
42void VirtualGamepad::SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x,
43 float gyro_y, float gyro_z, float accel_x, float accel_y,
44 float accel_z) {
45 const auto identifier = GetIdentifier(player_index);
46 const BasicMotion motion_data{
47 .gyro_x = gyro_x,
48 .gyro_y = gyro_y,
49 .gyro_z = gyro_z,
50 .accel_x = accel_x,
51 .accel_y = accel_y,
52 .accel_z = accel_z,
53 .delta_timestamp = delta_timestamp,
54 };
55 SetMotion(identifier, 0, motion_data);
56}
57
42void VirtualGamepad::ResetControllers() { 58void VirtualGamepad::ResetControllers() {
43 for (std::size_t i = 0; i < PlayerIndexCount; i++) { 59 for (std::size_t i = 0; i < PlayerIndexCount; i++) {
44 SetStickPosition(i, VirtualStick::Left, 0.0f, 0.0f); 60 SetStickPosition(i, VirtualStick::Left, 0.0f, 0.0f);
diff --git a/src/input_common/drivers/virtual_gamepad.h b/src/input_common/drivers/virtual_gamepad.h
index 3df91cc6f..dfbc45a28 100644
--- a/src/input_common/drivers/virtual_gamepad.h
+++ b/src/input_common/drivers/virtual_gamepad.h
@@ -52,7 +52,7 @@ public:
52 void SetButtonState(std::size_t player_index, VirtualButton button_id, bool value); 52 void SetButtonState(std::size_t player_index, VirtualButton button_id, bool value);
53 53
54 /** 54 /**
55 * Sets the status of all buttons bound with the key to released 55 * Sets the status of a stick to a specific player index
56 * @param player_index the player number that will take this action 56 * @param player_index the player number that will take this action
57 * @param axis_id the id of the axis to move 57 * @param axis_id the id of the axis to move
58 * @param x_value the position of the stick in the x axis 58 * @param x_value the position of the stick in the x axis
@@ -62,6 +62,16 @@ public:
62 void SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value, 62 void SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value,
63 float y_value); 63 float y_value);
64 64
65 /**
66 * Sets the status of the motion sensor to a specific player index
67 * @param player_index the player number that will take this action
68 * @param delta_timestamp time passed since last reading
69 * @param gyro_x,gyro_y,gyro_z the gyro sensor readings
70 * @param accel_x,accel_y,accel_z the acelerometer reading
71 */
72 void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
73 float gyro_z, float accel_x, float accel_y, float accel_z);
74
65 /// Restores all inputs into the neutral position 75 /// Restores all inputs into the neutral position
66 void ResetControllers(); 76 void ResetControllers();
67 77
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt
index 525b2363c..07e75f9d8 100644
--- a/src/shader_recompiler/CMakeLists.txt
+++ b/src/shader_recompiler/CMakeLists.txt
@@ -216,6 +216,7 @@ add_library(shader_recompiler STATIC
216 frontend/maxwell/translate_program.h 216 frontend/maxwell/translate_program.h
217 host_translate_info.h 217 host_translate_info.h
218 ir_opt/collect_shader_info_pass.cpp 218 ir_opt/collect_shader_info_pass.cpp
219 ir_opt/conditional_barrier_pass.cpp
219 ir_opt/constant_propagation_pass.cpp 220 ir_opt/constant_propagation_pass.cpp
220 ir_opt/dead_code_elimination_pass.cpp 221 ir_opt/dead_code_elimination_pass.cpp
221 ir_opt/dual_vertex_pass.cpp 222 ir_opt/dual_vertex_pass.cpp
@@ -223,6 +224,7 @@ add_library(shader_recompiler STATIC
223 ir_opt/identity_removal_pass.cpp 224 ir_opt/identity_removal_pass.cpp
224 ir_opt/layer_pass.cpp 225 ir_opt/layer_pass.cpp
225 ir_opt/lower_fp16_to_fp32.cpp 226 ir_opt/lower_fp16_to_fp32.cpp
227 ir_opt/lower_fp64_to_fp32.cpp
226 ir_opt/lower_int64_to_int32.cpp 228 ir_opt/lower_int64_to_int32.cpp
227 ir_opt/passes.h 229 ir_opt/passes.h
228 ir_opt/position_pass.cpp 230 ir_opt/position_pass.cpp
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
index 4b3043b65..0ce73f289 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
@@ -69,6 +69,11 @@ Id StorageAtomicU32(EmitContext& ctx, const IR::Value& binding, const IR::Value&
69Id StorageAtomicU64(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value, 69Id StorageAtomicU64(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value,
70 Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id), 70 Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id),
71 Id (Sirit::Module::*non_atomic_func)(Id, Id, Id)) { 71 Id (Sirit::Module::*non_atomic_func)(Id, Id, Id)) {
72 if (!ctx.profile.support_descriptor_aliasing) {
73 LOG_WARNING(Shader_SPIRV, "Descriptor aliasing not supported, this cannot be atomic.");
74 return ctx.ConstantNull(ctx.U64);
75 }
76
72 if (ctx.profile.support_int64_atomics) { 77 if (ctx.profile.support_int64_atomics) {
73 const Id pointer{StoragePointer(ctx, ctx.storage_types.U64, &StorageDefinitions::U64, 78 const Id pointer{StoragePointer(ctx, ctx.storage_types.U64, &StorageDefinitions::U64,
74 binding, offset, sizeof(u64))}; 79 binding, offset, sizeof(u64))};
@@ -86,6 +91,11 @@ Id StorageAtomicU64(EmitContext& ctx, const IR::Value& binding, const IR::Value&
86 91
87Id StorageAtomicU32x2(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value, 92Id StorageAtomicU32x2(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value,
88 Id (Sirit::Module::*non_atomic_func)(Id, Id, Id)) { 93 Id (Sirit::Module::*non_atomic_func)(Id, Id, Id)) {
94 if (!ctx.profile.support_descriptor_aliasing) {
95 LOG_WARNING(Shader_SPIRV, "Descriptor aliasing not supported, this cannot be atomic.");
96 return ctx.ConstantNull(ctx.U32[2]);
97 }
98
89 LOG_WARNING(Shader_SPIRV, "Int64 atomics not supported, fallback to non-atomic"); 99 LOG_WARNING(Shader_SPIRV, "Int64 atomics not supported, fallback to non-atomic");
90 const Id pointer{StoragePointer(ctx, ctx.storage_types.U32x2, &StorageDefinitions::U32x2, 100 const Id pointer{StoragePointer(ctx, ctx.storage_types.U32x2, &StorageDefinitions::U32x2,
91 binding, offset, sizeof(u32[2]))}; 101 binding, offset, sizeof(u32[2]))};
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 07c2b7b8a..2868fc57d 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -10,27 +10,6 @@
10 10
11namespace Shader::Backend::SPIRV { 11namespace Shader::Backend::SPIRV {
12namespace { 12namespace {
13struct AttrInfo {
14 Id pointer;
15 Id id;
16 bool needs_cast;
17};
18
19std::optional<AttrInfo> AttrTypes(EmitContext& ctx, u32 index) {
20 const AttributeType type{ctx.runtime_info.generic_input_types.at(index)};
21 switch (type) {
22 case AttributeType::Float:
23 return AttrInfo{ctx.input_f32, ctx.F32[1], false};
24 case AttributeType::UnsignedInt:
25 return AttrInfo{ctx.input_u32, ctx.U32[1], true};
26 case AttributeType::SignedInt:
27 return AttrInfo{ctx.input_s32, ctx.TypeInt(32, true), true};
28 case AttributeType::Disabled:
29 return std::nullopt;
30 }
31 throw InvalidArgument("Invalid attribute type {}", type);
32}
33
34template <typename... Args> 13template <typename... Args>
35Id AttrPointer(EmitContext& ctx, Id pointer_type, Id vertex, Id base, Args&&... args) { 14Id AttrPointer(EmitContext& ctx, Id pointer_type, Id vertex, Id base, Args&&... args) {
36 switch (ctx.stage) { 15 switch (ctx.stage) {
@@ -302,15 +281,26 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
302 const u32 element{static_cast<u32>(attr) % 4}; 281 const u32 element{static_cast<u32>(attr) % 4};
303 if (IR::IsGeneric(attr)) { 282 if (IR::IsGeneric(attr)) {
304 const u32 index{IR::GenericAttributeIndex(attr)}; 283 const u32 index{IR::GenericAttributeIndex(attr)};
305 const std::optional<AttrInfo> type{AttrTypes(ctx, index)}; 284 const auto& generic{ctx.input_generics.at(index)};
306 if (!type || !ctx.runtime_info.previous_stage_stores.Generic(index, element)) { 285 if (!ValidId(generic.id)) {
307 // Attribute is disabled or varying component is not written 286 // Attribute is disabled or varying component is not written
308 return ctx.Const(element == 3 ? 1.0f : 0.0f); 287 return ctx.Const(element == 3 ? 1.0f : 0.0f);
309 } 288 }
310 const Id generic_id{ctx.input_generics.at(index)}; 289 const Id pointer{
311 const Id pointer{AttrPointer(ctx, type->pointer, vertex, generic_id, ctx.Const(element))}; 290 AttrPointer(ctx, generic.pointer_type, vertex, generic.id, ctx.Const(element))};
312 const Id value{ctx.OpLoad(type->id, pointer)}; 291 const Id value{ctx.OpLoad(generic.component_type, pointer)};
313 return type->needs_cast ? ctx.OpBitcast(ctx.F32[1], value) : value; 292 return [&ctx, generic, value]() {
293 switch (generic.load_op) {
294 case InputGenericLoadOp::Bitcast:
295 return ctx.OpBitcast(ctx.F32[1], value);
296 case InputGenericLoadOp::SToF:
297 return ctx.OpConvertSToF(ctx.F32[1], value);
298 case InputGenericLoadOp::UToF:
299 return ctx.OpConvertUToF(ctx.F32[1], value);
300 default:
301 return value;
302 };
303 }();
314 } 304 }
315 switch (attr) { 305 switch (attr) {
316 case IR::Attribute::PrimitiveId: 306 case IR::Attribute::PrimitiveId:
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
index c5db19d09..77ff8c573 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
@@ -17,7 +17,22 @@ Id GetThreadId(EmitContext& ctx) {
17Id WarpExtract(EmitContext& ctx, Id value) { 17Id WarpExtract(EmitContext& ctx, Id value) {
18 const Id thread_id{GetThreadId(ctx)}; 18 const Id thread_id{GetThreadId(ctx)};
19 const Id local_index{ctx.OpShiftRightArithmetic(ctx.U32[1], thread_id, ctx.Const(5U))}; 19 const Id local_index{ctx.OpShiftRightArithmetic(ctx.U32[1], thread_id, ctx.Const(5U))};
20 return ctx.OpVectorExtractDynamic(ctx.U32[1], value, local_index); 20 if (ctx.profile.has_broken_spirv_subgroup_mask_vector_extract_dynamic) {
21 const Id c0_sel{ctx.OpSelect(ctx.U32[1], ctx.OpIEqual(ctx.U1, local_index, ctx.Const(0U)),
22 ctx.OpCompositeExtract(ctx.U32[1], value, 0U), ctx.Const(0U))};
23 const Id c1_sel{ctx.OpSelect(ctx.U32[1], ctx.OpIEqual(ctx.U1, local_index, ctx.Const(1U)),
24 ctx.OpCompositeExtract(ctx.U32[1], value, 1U), ctx.Const(0U))};
25 const Id c2_sel{ctx.OpSelect(ctx.U32[1], ctx.OpIEqual(ctx.U1, local_index, ctx.Const(2U)),
26 ctx.OpCompositeExtract(ctx.U32[1], value, 2U), ctx.Const(0U))};
27 const Id c3_sel{ctx.OpSelect(ctx.U32[1], ctx.OpIEqual(ctx.U1, local_index, ctx.Const(3U)),
28 ctx.OpCompositeExtract(ctx.U32[1], value, 3U), ctx.Const(0U))};
29 const Id c0_or_c1{ctx.OpBitwiseOr(ctx.U32[1], c0_sel, c1_sel)};
30 const Id c2_or_c3{ctx.OpBitwiseOr(ctx.U32[1], c2_sel, c3_sel)};
31 const Id c0_or_c1_or_c2_or_c3{ctx.OpBitwiseOr(ctx.U32[1], c0_or_c1, c2_or_c3)};
32 return c0_or_c1_or_c2_or_c3;
33 } else {
34 return ctx.OpVectorExtractDynamic(ctx.U32[1], value, local_index);
35 }
21} 36}
22 37
23Id LoadMask(EmitContext& ctx, Id mask) { 38Id LoadMask(EmitContext& ctx, Id mask) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 47739794f..fd15f47ea 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -25,12 +25,6 @@ enum class Operation {
25 FPMax, 25 FPMax,
26}; 26};
27 27
28struct AttrInfo {
29 Id pointer;
30 Id id;
31 bool needs_cast;
32};
33
34Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { 28Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
35 const spv::ImageFormat format{spv::ImageFormat::Unknown}; 29 const spv::ImageFormat format{spv::ImageFormat::Unknown};
36 const Id type{ctx.F32[1]}; 30 const Id type{ctx.F32[1]};
@@ -206,23 +200,37 @@ Id GetAttributeType(EmitContext& ctx, AttributeType type) {
206 return ctx.TypeVector(ctx.TypeInt(32, true), 4); 200 return ctx.TypeVector(ctx.TypeInt(32, true), 4);
207 case AttributeType::UnsignedInt: 201 case AttributeType::UnsignedInt:
208 return ctx.U32[4]; 202 return ctx.U32[4];
203 case AttributeType::SignedScaled:
204 return ctx.profile.support_scaled_attributes ? ctx.F32[4]
205 : ctx.TypeVector(ctx.TypeInt(32, true), 4);
206 case AttributeType::UnsignedScaled:
207 return ctx.profile.support_scaled_attributes ? ctx.F32[4] : ctx.U32[4];
209 case AttributeType::Disabled: 208 case AttributeType::Disabled:
210 break; 209 break;
211 } 210 }
212 throw InvalidArgument("Invalid attribute type {}", type); 211 throw InvalidArgument("Invalid attribute type {}", type);
213} 212}
214 213
215std::optional<AttrInfo> AttrTypes(EmitContext& ctx, u32 index) { 214InputGenericInfo GetAttributeInfo(EmitContext& ctx, AttributeType type, Id id) {
216 const AttributeType type{ctx.runtime_info.generic_input_types.at(index)};
217 switch (type) { 215 switch (type) {
218 case AttributeType::Float: 216 case AttributeType::Float:
219 return AttrInfo{ctx.input_f32, ctx.F32[1], false}; 217 return InputGenericInfo{id, ctx.input_f32, ctx.F32[1], InputGenericLoadOp::None};
220 case AttributeType::UnsignedInt: 218 case AttributeType::UnsignedInt:
221 return AttrInfo{ctx.input_u32, ctx.U32[1], true}; 219 return InputGenericInfo{id, ctx.input_u32, ctx.U32[1], InputGenericLoadOp::Bitcast};
222 case AttributeType::SignedInt: 220 case AttributeType::SignedInt:
223 return AttrInfo{ctx.input_s32, ctx.TypeInt(32, true), true}; 221 return InputGenericInfo{id, ctx.input_s32, ctx.TypeInt(32, true),
222 InputGenericLoadOp::Bitcast};
223 case AttributeType::SignedScaled:
224 return ctx.profile.support_scaled_attributes
225 ? InputGenericInfo{id, ctx.input_f32, ctx.F32[1], InputGenericLoadOp::None}
226 : InputGenericInfo{id, ctx.input_s32, ctx.TypeInt(32, true),
227 InputGenericLoadOp::SToF};
228 case AttributeType::UnsignedScaled:
229 return ctx.profile.support_scaled_attributes
230 ? InputGenericInfo{id, ctx.input_f32, ctx.F32[1], InputGenericLoadOp::None}
231 : InputGenericInfo{id, ctx.input_u32, ctx.U32[1], InputGenericLoadOp::UToF};
224 case AttributeType::Disabled: 232 case AttributeType::Disabled:
225 return std::nullopt; 233 return InputGenericInfo{};
226 } 234 }
227 throw InvalidArgument("Invalid attribute type {}", type); 235 throw InvalidArgument("Invalid attribute type {}", type);
228} 236}
@@ -746,18 +754,29 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
746 continue; 754 continue;
747 } 755 }
748 AddLabel(labels[label_index]); 756 AddLabel(labels[label_index]);
749 const auto type{AttrTypes(*this, static_cast<u32>(index))}; 757 const auto& generic{input_generics.at(index)};
750 if (!type) { 758 const Id generic_id{generic.id};
759 if (!ValidId(generic_id)) {
751 OpReturnValue(Const(0.0f)); 760 OpReturnValue(Const(0.0f));
752 ++label_index; 761 ++label_index;
753 continue; 762 continue;
754 } 763 }
755 const Id generic_id{input_generics.at(index)}; 764 const Id pointer{
756 const Id pointer{is_array 765 is_array ? OpAccessChain(generic.pointer_type, generic_id, vertex, masked_index)
757 ? OpAccessChain(type->pointer, generic_id, vertex, masked_index) 766 : OpAccessChain(generic.pointer_type, generic_id, masked_index)};
758 : OpAccessChain(type->pointer, generic_id, masked_index)}; 767 const Id value{OpLoad(generic.component_type, pointer)};
759 const Id value{OpLoad(type->id, pointer)}; 768 const Id result{[this, generic, value]() {
760 const Id result{type->needs_cast ? OpBitcast(F32[1], value) : value}; 769 switch (generic.load_op) {
770 case InputGenericLoadOp::Bitcast:
771 return OpBitcast(F32[1], value);
772 case InputGenericLoadOp::SToF:
773 return OpConvertSToF(F32[1], value);
774 case InputGenericLoadOp::UToF:
775 return OpConvertUToF(F32[1], value);
776 default:
777 return value;
778 };
779 }()};
761 OpReturnValue(result); 780 OpReturnValue(result);
762 ++label_index; 781 ++label_index;
763 } 782 }
@@ -1457,7 +1476,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
1457 const Id id{DefineInput(*this, type, true)}; 1476 const Id id{DefineInput(*this, type, true)};
1458 Decorate(id, spv::Decoration::Location, static_cast<u32>(index)); 1477 Decorate(id, spv::Decoration::Location, static_cast<u32>(index));
1459 Name(id, fmt::format("in_attr{}", index)); 1478 Name(id, fmt::format("in_attr{}", index));
1460 input_generics[index] = id; 1479 input_generics[index] = GetAttributeInfo(*this, input_type, id);
1461 1480
1462 if (info.passthrough.Generic(index) && profile.support_geometry_shader_passthrough) { 1481 if (info.passthrough.Generic(index) && profile.support_geometry_shader_passthrough) {
1463 Decorate(id, spv::Decoration::PassthroughNV); 1482 Decorate(id, spv::Decoration::PassthroughNV);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index 768a4fbb5..e63330f11 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -95,6 +95,20 @@ struct StorageDefinitions {
95 Id U32x4{}; 95 Id U32x4{};
96}; 96};
97 97
98enum class InputGenericLoadOp {
99 None,
100 Bitcast,
101 SToF,
102 UToF,
103};
104
105struct InputGenericInfo {
106 Id id;
107 Id pointer_type;
108 Id component_type;
109 InputGenericLoadOp load_op;
110};
111
98struct GenericElementInfo { 112struct GenericElementInfo {
99 Id id{}; 113 Id id{};
100 u32 first_element{}; 114 u32 first_element{};
@@ -283,7 +297,7 @@ public:
283 297
284 bool need_input_position_indirect{}; 298 bool need_input_position_indirect{};
285 Id input_position{}; 299 Id input_position{};
286 std::array<Id, 32> input_generics{}; 300 std::array<InputGenericInfo, 32> input_generics{};
287 301
288 Id output_point_size{}; 302 Id output_point_size{};
289 Id output_position{}; 303 Id output_position{};
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
index 17a6d4888..928b35561 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
@@ -280,12 +280,18 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
280 RemoveUnreachableBlocks(program); 280 RemoveUnreachableBlocks(program);
281 281
282 // Replace instructions before the SSA rewrite 282 // Replace instructions before the SSA rewrite
283 if (!host_info.support_float64) {
284 Optimization::LowerFp64ToFp32(program);
285 }
283 if (!host_info.support_float16) { 286 if (!host_info.support_float16) {
284 Optimization::LowerFp16ToFp32(program); 287 Optimization::LowerFp16ToFp32(program);
285 } 288 }
286 if (!host_info.support_int64) { 289 if (!host_info.support_int64) {
287 Optimization::LowerInt64ToInt32(program); 290 Optimization::LowerInt64ToInt32(program);
288 } 291 }
292 if (!host_info.support_conditional_barrier) {
293 Optimization::ConditionalBarrierPass(program);
294 }
289 Optimization::SsaRewritePass(program); 295 Optimization::SsaRewritePass(program);
290 296
291 Optimization::ConstantPropagationPass(env, program); 297 Optimization::ConstantPropagationPass(env, program);
diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h
index 2aaa6c5ea..7d2ded907 100644
--- a/src/shader_recompiler/host_translate_info.h
+++ b/src/shader_recompiler/host_translate_info.h
@@ -10,6 +10,7 @@ namespace Shader {
10 10
11/// Misc information about the host 11/// Misc information about the host
12struct HostTranslateInfo { 12struct HostTranslateInfo {
13 bool support_float64{}; ///< True when the device supports 64-bit floats
13 bool support_float16{}; ///< True when the device supports 16-bit floats 14 bool support_float16{}; ///< True when the device supports 16-bit floats
14 bool support_int64{}; ///< True when the device supports 64-bit integers 15 bool support_int64{}; ///< True when the device supports 64-bit integers
15 bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered 16 bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered
@@ -17,6 +18,8 @@ struct HostTranslateInfo {
17 bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS 18 bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS
18 bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry 19 bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry
19 ///< passthrough shaders 20 ///< passthrough shaders
21 bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
22 ///< control flow
20}; 23};
21 24
22} // namespace Shader 25} // namespace Shader
diff --git a/src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp b/src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp
new file mode 100644
index 000000000..c3ed27f4f
--- /dev/null
+++ b/src/shader_recompiler/ir_opt/conditional_barrier_pass.cpp
@@ -0,0 +1,44 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "shader_recompiler/frontend/ir/program.h"
5#include "shader_recompiler/ir_opt/passes.h"
6
7namespace Shader::Optimization {
8
9void ConditionalBarrierPass(IR::Program& program) {
10 s32 conditional_control_flow_count{0};
11 s32 conditional_return_count{0};
12 for (IR::AbstractSyntaxNode& node : program.syntax_list) {
13 switch (node.type) {
14 case IR::AbstractSyntaxNode::Type::If:
15 case IR::AbstractSyntaxNode::Type::Loop:
16 conditional_control_flow_count++;
17 break;
18 case IR::AbstractSyntaxNode::Type::EndIf:
19 case IR::AbstractSyntaxNode::Type::Repeat:
20 conditional_control_flow_count--;
21 break;
22 case IR::AbstractSyntaxNode::Type::Unreachable:
23 case IR::AbstractSyntaxNode::Type::Return:
24 if (conditional_control_flow_count > 0) {
25 conditional_return_count++;
26 }
27 break;
28 case IR::AbstractSyntaxNode::Type::Block:
29 for (IR::Inst& inst : node.data.block->Instructions()) {
30 if ((conditional_control_flow_count > 0 || conditional_return_count > 0) &&
31 inst.GetOpcode() == IR::Opcode::Barrier) {
32 LOG_WARNING(Shader, "Barrier within conditional control flow");
33 inst.ReplaceOpcode(IR::Opcode::Identity);
34 }
35 }
36 break;
37 default:
38 break;
39 }
40 }
41 ASSERT(conditional_control_flow_count == 0);
42}
43
44} // namespace Shader::Optimization
diff --git a/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp b/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp
new file mode 100644
index 000000000..5db7a38ad
--- /dev/null
+++ b/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp
@@ -0,0 +1,185 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "shader_recompiler/frontend/ir/ir_emitter.h"
5#include "shader_recompiler/frontend/ir/opcodes.h"
6#include "shader_recompiler/frontend/ir/value.h"
7#include "shader_recompiler/ir_opt/passes.h"
8
9namespace Shader::Optimization {
10namespace {
11
12constexpr s32 F64ToF32Exp = +1023 - 127;
13constexpr s32 F32ToF64Exp = +127 - 1023;
14
15IR::F32 PackedF64ToF32(IR::IREmitter& ir, const IR::Value& packed) {
16 const IR::U32 lo{ir.CompositeExtract(packed, 0)};
17 const IR::U32 hi{ir.CompositeExtract(packed, 1)};
18 const IR::U32 sign{ir.BitFieldExtract(hi, ir.Imm32(31), ir.Imm32(1))};
19 const IR::U32 exp{ir.BitFieldExtract(hi, ir.Imm32(20), ir.Imm32(11))};
20 const IR::U32 mantissa_hi{ir.BitFieldExtract(hi, ir.Imm32(0), ir.Imm32(20))};
21 const IR::U32 mantissa_lo{ir.BitFieldExtract(lo, ir.Imm32(29), ir.Imm32(3))};
22 const IR::U32 mantissa{
23 ir.BitwiseOr(ir.ShiftLeftLogical(mantissa_hi, ir.Imm32(3)), mantissa_lo)};
24 const IR::U32 exp_if_subnorm{
25 ir.Select(ir.IEqual(exp, ir.Imm32(0)), ir.Imm32(0), ir.IAdd(exp, ir.Imm32(F64ToF32Exp)))};
26 const IR::U32 exp_if_infnan{
27 ir.Select(ir.IEqual(exp, ir.Imm32(0x7ff)), ir.Imm32(0xff), exp_if_subnorm)};
28 const IR::U32 result{
29 ir.BitwiseOr(ir.ShiftLeftLogical(sign, ir.Imm32(31)),
30 ir.BitwiseOr(ir.ShiftLeftLogical(exp_if_infnan, ir.Imm32(23)), mantissa))};
31 return ir.BitCast<IR::F32>(result);
32}
33
34IR::Value F32ToPackedF64(IR::IREmitter& ir, const IR::Value& raw) {
35 const IR::U32 value{ir.BitCast<IR::U32>(IR::F32(raw))};
36 const IR::U32 sign{ir.BitFieldExtract(value, ir.Imm32(31), ir.Imm32(1))};
37 const IR::U32 exp{ir.BitFieldExtract(value, ir.Imm32(23), ir.Imm32(8))};
38 const IR::U32 mantissa{ir.BitFieldExtract(value, ir.Imm32(0), ir.Imm32(23))};
39 const IR::U32 mantissa_hi{ir.BitFieldExtract(mantissa, ir.Imm32(3), ir.Imm32(20))};
40 const IR::U32 mantissa_lo{ir.BitFieldExtract(mantissa, ir.Imm32(0), ir.Imm32(3))};
41 const IR::U32 exp_if_subnorm{
42 ir.Select(ir.IEqual(exp, ir.Imm32(0)), ir.Imm32(0), ir.IAdd(exp, ir.Imm32(F32ToF64Exp)))};
43 const IR::U32 exp_if_infnan{
44 ir.Select(ir.IEqual(exp, ir.Imm32(0xff)), ir.Imm32(0x7ff), exp_if_subnorm)};
45 const IR::U32 lo{ir.ShiftLeftLogical(mantissa_lo, ir.Imm32(29))};
46 const IR::U32 hi{
47 ir.BitwiseOr(ir.ShiftLeftLogical(sign, ir.Imm32(31)),
48 ir.BitwiseOr(ir.ShiftLeftLogical(exp_if_infnan, ir.Imm32(20)), mantissa_hi))};
49 return ir.CompositeConstruct(lo, hi);
50}
51
52IR::Opcode Replace(IR::Opcode op) {
53 switch (op) {
54 case IR::Opcode::FPAbs64:
55 return IR::Opcode::FPAbs32;
56 case IR::Opcode::FPAdd64:
57 return IR::Opcode::FPAdd32;
58 case IR::Opcode::FPCeil64:
59 return IR::Opcode::FPCeil32;
60 case IR::Opcode::FPFloor64:
61 return IR::Opcode::FPFloor32;
62 case IR::Opcode::FPFma64:
63 return IR::Opcode::FPFma32;
64 case IR::Opcode::FPMul64:
65 return IR::Opcode::FPMul32;
66 case IR::Opcode::FPNeg64:
67 return IR::Opcode::FPNeg32;
68 case IR::Opcode::FPRoundEven64:
69 return IR::Opcode::FPRoundEven32;
70 case IR::Opcode::FPSaturate64:
71 return IR::Opcode::FPSaturate32;
72 case IR::Opcode::FPClamp64:
73 return IR::Opcode::FPClamp32;
74 case IR::Opcode::FPTrunc64:
75 return IR::Opcode::FPTrunc32;
76 case IR::Opcode::CompositeConstructF64x2:
77 return IR::Opcode::CompositeConstructF32x2;
78 case IR::Opcode::CompositeConstructF64x3:
79 return IR::Opcode::CompositeConstructF32x3;
80 case IR::Opcode::CompositeConstructF64x4:
81 return IR::Opcode::CompositeConstructF32x4;
82 case IR::Opcode::CompositeExtractF64x2:
83 return IR::Opcode::CompositeExtractF32x2;
84 case IR::Opcode::CompositeExtractF64x3:
85 return IR::Opcode::CompositeExtractF32x3;
86 case IR::Opcode::CompositeExtractF64x4:
87 return IR::Opcode::CompositeExtractF32x4;
88 case IR::Opcode::CompositeInsertF64x2:
89 return IR::Opcode::CompositeInsertF32x2;
90 case IR::Opcode::CompositeInsertF64x3:
91 return IR::Opcode::CompositeInsertF32x3;
92 case IR::Opcode::CompositeInsertF64x4:
93 return IR::Opcode::CompositeInsertF32x4;
94 case IR::Opcode::FPOrdEqual64:
95 return IR::Opcode::FPOrdEqual32;
96 case IR::Opcode::FPUnordEqual64:
97 return IR::Opcode::FPUnordEqual32;
98 case IR::Opcode::FPOrdNotEqual64:
99 return IR::Opcode::FPOrdNotEqual32;
100 case IR::Opcode::FPUnordNotEqual64:
101 return IR::Opcode::FPUnordNotEqual32;
102 case IR::Opcode::FPOrdLessThan64:
103 return IR::Opcode::FPOrdLessThan32;
104 case IR::Opcode::FPUnordLessThan64:
105 return IR::Opcode::FPUnordLessThan32;
106 case IR::Opcode::FPOrdGreaterThan64:
107 return IR::Opcode::FPOrdGreaterThan32;
108 case IR::Opcode::FPUnordGreaterThan64:
109 return IR::Opcode::FPUnordGreaterThan32;
110 case IR::Opcode::FPOrdLessThanEqual64:
111 return IR::Opcode::FPOrdLessThanEqual32;
112 case IR::Opcode::FPUnordLessThanEqual64:
113 return IR::Opcode::FPUnordLessThanEqual32;
114 case IR::Opcode::FPOrdGreaterThanEqual64:
115 return IR::Opcode::FPOrdGreaterThanEqual32;
116 case IR::Opcode::FPUnordGreaterThanEqual64:
117 return IR::Opcode::FPUnordGreaterThanEqual32;
118 case IR::Opcode::FPIsNan64:
119 return IR::Opcode::FPIsNan32;
120 case IR::Opcode::ConvertS16F64:
121 return IR::Opcode::ConvertS16F32;
122 case IR::Opcode::ConvertS32F64:
123 return IR::Opcode::ConvertS32F32;
124 case IR::Opcode::ConvertS64F64:
125 return IR::Opcode::ConvertS64F32;
126 case IR::Opcode::ConvertU16F64:
127 return IR::Opcode::ConvertU16F32;
128 case IR::Opcode::ConvertU32F64:
129 return IR::Opcode::ConvertU32F32;
130 case IR::Opcode::ConvertU64F64:
131 return IR::Opcode::ConvertU64F32;
132 case IR::Opcode::ConvertF32F64:
133 return IR::Opcode::Identity;
134 case IR::Opcode::ConvertF64F32:
135 return IR::Opcode::Identity;
136 case IR::Opcode::ConvertF64S8:
137 return IR::Opcode::ConvertF32S8;
138 case IR::Opcode::ConvertF64S16:
139 return IR::Opcode::ConvertF32S16;
140 case IR::Opcode::ConvertF64S32:
141 return IR::Opcode::ConvertF32S32;
142 case IR::Opcode::ConvertF64S64:
143 return IR::Opcode::ConvertF32S64;
144 case IR::Opcode::ConvertF64U8:
145 return IR::Opcode::ConvertF32U8;
146 case IR::Opcode::ConvertF64U16:
147 return IR::Opcode::ConvertF32U16;
148 case IR::Opcode::ConvertF64U32:
149 return IR::Opcode::ConvertF32U32;
150 case IR::Opcode::ConvertF64U64:
151 return IR::Opcode::ConvertF32U64;
152 default:
153 return op;
154 }
155}
156
157void Lower(IR::Block& block, IR::Inst& inst) {
158 switch (inst.GetOpcode()) {
159 case IR::Opcode::PackDouble2x32: {
160 IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst));
161 inst.ReplaceUsesWith(PackedF64ToF32(ir, inst.Arg(0)));
162 break;
163 }
164 case IR::Opcode::UnpackDouble2x32: {
165 IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst));
166 inst.ReplaceUsesWith(F32ToPackedF64(ir, inst.Arg(0)));
167 break;
168 }
169 default:
170 inst.ReplaceOpcode(Replace(inst.GetOpcode()));
171 break;
172 }
173}
174
175} // Anonymous namespace
176
177void LowerFp64ToFp32(IR::Program& program) {
178 for (IR::Block* const block : program.blocks) {
179 for (IR::Inst& inst : block->Instructions()) {
180 Lower(*block, inst);
181 }
182 }
183}
184
185} // namespace Shader::Optimization
diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h
index 1f8f2ba95..629d18fa1 100644
--- a/src/shader_recompiler/ir_opt/passes.h
+++ b/src/shader_recompiler/ir_opt/passes.h
@@ -13,10 +13,12 @@ struct HostTranslateInfo;
13namespace Shader::Optimization { 13namespace Shader::Optimization {
14 14
15void CollectShaderInfoPass(Environment& env, IR::Program& program); 15void CollectShaderInfoPass(Environment& env, IR::Program& program);
16void ConditionalBarrierPass(IR::Program& program);
16void ConstantPropagationPass(Environment& env, IR::Program& program); 17void ConstantPropagationPass(Environment& env, IR::Program& program);
17void DeadCodeEliminationPass(IR::Program& program); 18void DeadCodeEliminationPass(IR::Program& program);
18void GlobalMemoryToStorageBufferPass(IR::Program& program); 19void GlobalMemoryToStorageBufferPass(IR::Program& program);
19void IdentityRemovalPass(IR::Program& program); 20void IdentityRemovalPass(IR::Program& program);
21void LowerFp64ToFp32(IR::Program& program);
20void LowerFp16ToFp32(IR::Program& program); 22void LowerFp16ToFp32(IR::Program& program);
21void LowerInt64ToInt32(IR::Program& program); 23void LowerInt64ToInt32(IR::Program& program);
22void RescalingPass(IR::Program& program); 24void RescalingPass(IR::Program& program);
diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h
index 9f88fb440..9ca97f6a4 100644
--- a/src/shader_recompiler/profile.h
+++ b/src/shader_recompiler/profile.h
@@ -43,6 +43,7 @@ struct Profile {
43 bool support_gl_variable_aoffi{}; 43 bool support_gl_variable_aoffi{};
44 bool support_gl_sparse_textures{}; 44 bool support_gl_sparse_textures{};
45 bool support_gl_derivative_control{}; 45 bool support_gl_derivative_control{};
46 bool support_scaled_attributes{};
46 47
47 bool warp_size_potentially_larger_than_guest{}; 48 bool warp_size_potentially_larger_than_guest{};
48 49
@@ -77,6 +78,8 @@ struct Profile {
77 bool has_gl_bool_ref_bug{}; 78 bool has_gl_bool_ref_bug{};
78 /// Ignores SPIR-V ordered vs unordered using GLSL semantics 79 /// Ignores SPIR-V ordered vs unordered using GLSL semantics
79 bool ignore_nan_fp_comparisons{}; 80 bool ignore_nan_fp_comparisons{};
81 /// Some drivers have broken support for OpVectorExtractDynamic on subgroup mask inputs
82 bool has_broken_spirv_subgroup_mask_vector_extract_dynamic{};
80 83
81 u32 gl_max_compute_smem_size{}; 84 u32 gl_max_compute_smem_size{};
82}; 85};
diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h
index 549b81ef7..3b63c249f 100644
--- a/src/shader_recompiler/runtime_info.h
+++ b/src/shader_recompiler/runtime_info.h
@@ -17,6 +17,8 @@ enum class AttributeType : u8 {
17 Float, 17 Float,
18 SignedInt, 18 SignedInt,
19 UnsignedInt, 19 UnsignedInt,
20 SignedScaled,
21 UnsignedScaled,
20 Disabled, 22 Disabled,
21}; 23};
22 24
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 308d013d6..bf6439530 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -133,8 +133,8 @@ add_library(video_core STATIC
133 renderer_opengl/gl_shader_util.h 133 renderer_opengl/gl_shader_util.h
134 renderer_opengl/gl_state_tracker.cpp 134 renderer_opengl/gl_state_tracker.cpp
135 renderer_opengl/gl_state_tracker.h 135 renderer_opengl/gl_state_tracker.h
136 renderer_opengl/gl_stream_buffer.cpp 136 renderer_opengl/gl_staging_buffer_pool.cpp
137 renderer_opengl/gl_stream_buffer.h 137 renderer_opengl/gl_staging_buffer_pool.h
138 renderer_opengl/gl_texture_cache.cpp 138 renderer_opengl/gl_texture_cache.cpp
139 renderer_opengl/gl_texture_cache.h 139 renderer_opengl/gl_texture_cache.h
140 renderer_opengl/gl_texture_cache_base.cpp 140 renderer_opengl/gl_texture_cache_base.cpp
@@ -281,7 +281,7 @@ create_target_directory_groups(video_core)
281target_link_libraries(video_core PUBLIC common core) 281target_link_libraries(video_core PUBLIC common core)
282target_link_libraries(video_core PUBLIC glad shader_recompiler stb) 282target_link_libraries(video_core PUBLIC glad shader_recompiler stb)
283 283
284if (YUZU_USE_BUNDLED_FFMPEG AND NOT WIN32) 284if (YUZU_USE_BUNDLED_FFMPEG AND NOT (WIN32 OR ANDROID))
285 add_dependencies(video_core ffmpeg-build) 285 add_dependencies(video_core ffmpeg-build)
286endif() 286endif()
287 287
@@ -345,3 +345,7 @@ endif()
345if (YUZU_ENABLE_LTO) 345if (YUZU_ENABLE_LTO)
346 set_property(TARGET video_core PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 346 set_property(TARGET video_core PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
347endif() 347endif()
348
349if (ANDROID AND ARCHITECTURE_arm64)
350 target_link_libraries(video_core PRIVATE adrenotools)
351endif()
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index f1ad5f7cb..251a4a880 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -478,7 +478,6 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
478 478
479 if (committed_ranges.empty()) { 479 if (committed_ranges.empty()) {
480 if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { 480 if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
481
482 async_buffers.emplace_back(std::optional<Async_Buffer>{}); 481 async_buffers.emplace_back(std::optional<Async_Buffer>{});
483 } 482 }
484 return; 483 return;
@@ -539,7 +538,6 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
539 committed_ranges.clear(); 538 committed_ranges.clear();
540 if (downloads.empty()) { 539 if (downloads.empty()) {
541 if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { 540 if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
542
543 async_buffers.emplace_back(std::optional<Async_Buffer>{}); 541 async_buffers.emplace_back(std::optional<Async_Buffer>{});
544 } 542 }
545 return; 543 return;
@@ -691,7 +689,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
691 const u32 size = channel_state->index_buffer.size; 689 const u32 size = channel_state->index_buffer.size;
692 const auto& draw_state = maxwell3d->draw_manager->GetDrawState(); 690 const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
693 if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] { 691 if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
694 if constexpr (USE_MEMORY_MAPS) { 692 if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
695 auto upload_staging = runtime.UploadStagingBuffer(size); 693 auto upload_staging = runtime.UploadStagingBuffer(size);
696 std::array<BufferCopy, 1> copies{ 694 std::array<BufferCopy, 1> copies{
697 {BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}}; 695 {BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
@@ -717,20 +715,38 @@ void BufferCache<P>::BindHostIndexBuffer() {
717 715
718template <class P> 716template <class P>
719void BufferCache<P>::BindHostVertexBuffers() { 717void BufferCache<P>::BindHostVertexBuffers() {
718 HostBindings host_bindings;
719 bool any_valid{false};
720 auto& flags = maxwell3d->dirty.flags; 720 auto& flags = maxwell3d->dirty.flags;
721 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { 721 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
722 const Binding& binding = channel_state->vertex_buffers[index];
723 Buffer& buffer = slot_buffers[binding.buffer_id];
724 TouchBuffer(buffer, binding.buffer_id);
725 SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
726 if (!flags[Dirty::VertexBuffer0 + index]) { 722 if (!flags[Dirty::VertexBuffer0 + index]) {
727 continue; 723 continue;
728 } 724 }
729 flags[Dirty::VertexBuffer0 + index] = false; 725 host_bindings.min_index = std::min(host_bindings.min_index, index);
726 host_bindings.max_index = std::max(host_bindings.max_index, index);
727 any_valid = true;
728 }
730 729
731 const u32 stride = maxwell3d->regs.vertex_streams[index].stride; 730 if (any_valid) {
732 const u32 offset = buffer.Offset(binding.cpu_addr); 731 host_bindings.max_index++;
733 runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride); 732 for (u32 index = host_bindings.min_index; index < host_bindings.max_index; index++) {
733 flags[Dirty::VertexBuffer0 + index] = false;
734
735 const Binding& binding = channel_state->vertex_buffers[index];
736 Buffer& buffer = slot_buffers[binding.buffer_id];
737
738 TouchBuffer(buffer, binding.buffer_id);
739 SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
740
741 const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
742 const u32 offset = buffer.Offset(binding.cpu_addr);
743
744 host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer));
745 host_bindings.offsets.push_back(offset);
746 host_bindings.sizes.push_back(binding.size);
747 host_bindings.strides.push_back(stride);
748 }
749 runtime.BindVertexBuffers(host_bindings);
734 } 750 }
735} 751}
736 752
@@ -884,15 +900,25 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
884 if (maxwell3d->regs.transform_feedback_enabled == 0) { 900 if (maxwell3d->regs.transform_feedback_enabled == 0) {
885 return; 901 return;
886 } 902 }
903 HostBindings host_bindings;
887 for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { 904 for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
888 const Binding& binding = channel_state->transform_feedback_buffers[index]; 905 const Binding& binding = channel_state->transform_feedback_buffers[index];
906 if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
907 maxwell3d->regs.transform_feedback.controls[index].stride == 0) {
908 break;
909 }
889 Buffer& buffer = slot_buffers[binding.buffer_id]; 910 Buffer& buffer = slot_buffers[binding.buffer_id];
890 TouchBuffer(buffer, binding.buffer_id); 911 TouchBuffer(buffer, binding.buffer_id);
891 const u32 size = binding.size; 912 const u32 size = binding.size;
892 SynchronizeBuffer(buffer, binding.cpu_addr, size); 913 SynchronizeBuffer(buffer, binding.cpu_addr, size);
893 914
894 const u32 offset = buffer.Offset(binding.cpu_addr); 915 const u32 offset = buffer.Offset(binding.cpu_addr);
895 runtime.BindTransformFeedbackBuffer(index, buffer, offset, size); 916 host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer));
917 host_bindings.offsets.push_back(offset);
918 host_bindings.sizes.push_back(binding.size);
919 }
920 if (host_bindings.buffers.size() > 0) {
921 runtime.BindTransformFeedbackBuffers(host_bindings);
896 } 922 }
897} 923}
898 924
@@ -1462,7 +1488,7 @@ bool BufferCache<P>::SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr,
1462template <class P> 1488template <class P>
1463void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, 1489void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
1464 std::span<BufferCopy> copies) { 1490 std::span<BufferCopy> copies) {
1465 if constexpr (USE_MEMORY_MAPS) { 1491 if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
1466 MappedUploadMemory(buffer, total_size_bytes, copies); 1492 MappedUploadMemory(buffer, total_size_bytes, copies);
1467 } else { 1493 } else {
1468 ImmediateUploadMemory(buffer, largest_copy, copies); 1494 ImmediateUploadMemory(buffer, largest_copy, copies);
@@ -1473,7 +1499,7 @@ template <class P>
1473void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer, 1499void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
1474 [[maybe_unused]] u64 largest_copy, 1500 [[maybe_unused]] u64 largest_copy,
1475 [[maybe_unused]] std::span<const BufferCopy> copies) { 1501 [[maybe_unused]] std::span<const BufferCopy> copies) {
1476 if constexpr (!USE_MEMORY_MAPS) { 1502 if constexpr (!USE_MEMORY_MAPS_FOR_UPLOADS) {
1477 std::span<u8> immediate_buffer; 1503 std::span<u8> immediate_buffer;
1478 for (const BufferCopy& copy : copies) { 1504 for (const BufferCopy& copy : copies) {
1479 std::span<const u8> upload_span; 1505 std::span<const u8> upload_span;
@@ -1532,7 +1558,7 @@ bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
1532 auto& buffer = slot_buffers[buffer_id]; 1558 auto& buffer = slot_buffers[buffer_id];
1533 SynchronizeBuffer(buffer, dest_address, static_cast<u32>(copy_size)); 1559 SynchronizeBuffer(buffer, dest_address, static_cast<u32>(copy_size));
1534 1560
1535 if constexpr (USE_MEMORY_MAPS) { 1561 if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
1536 auto upload_staging = runtime.UploadStagingBuffer(copy_size); 1562 auto upload_staging = runtime.UploadStagingBuffer(copy_size);
1537 std::array copies{BufferCopy{ 1563 std::array copies{BufferCopy{
1538 .src_offset = upload_staging.offset, 1564 .src_offset = upload_staging.offset,
@@ -1618,6 +1644,8 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
1618 1644
1619template <class P> 1645template <class P>
1620void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { 1646void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
1647 bool dirty_index{false};
1648 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> dirty_vertex_buffers;
1621 const auto scalar_replace = [buffer_id](Binding& binding) { 1649 const auto scalar_replace = [buffer_id](Binding& binding) {
1622 if (binding.buffer_id == buffer_id) { 1650 if (binding.buffer_id == buffer_id) {
1623 binding.buffer_id = BufferId{}; 1651 binding.buffer_id = BufferId{};
@@ -1626,8 +1654,19 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
1626 const auto replace = [scalar_replace](std::span<Binding> bindings) { 1654 const auto replace = [scalar_replace](std::span<Binding> bindings) {
1627 std::ranges::for_each(bindings, scalar_replace); 1655 std::ranges::for_each(bindings, scalar_replace);
1628 }; 1656 };
1629 scalar_replace(channel_state->index_buffer); 1657
1630 replace(channel_state->vertex_buffers); 1658 if (channel_state->index_buffer.buffer_id == buffer_id) {
1659 channel_state->index_buffer.buffer_id = BufferId{};
1660 dirty_index = true;
1661 }
1662
1663 for (u32 index = 0; index < channel_state->vertex_buffers.size(); index++) {
1664 auto& binding = channel_state->vertex_buffers[index];
1665 if (binding.buffer_id == buffer_id) {
1666 binding.buffer_id = BufferId{};
1667 dirty_vertex_buffers.push_back(index);
1668 }
1669 }
1631 std::ranges::for_each(channel_state->uniform_buffers, replace); 1670 std::ranges::for_each(channel_state->uniform_buffers, replace);
1632 std::ranges::for_each(channel_state->storage_buffers, replace); 1671 std::ranges::for_each(channel_state->storage_buffers, replace);
1633 replace(channel_state->transform_feedback_buffers); 1672 replace(channel_state->transform_feedback_buffers);
@@ -1644,20 +1683,21 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
1644 delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id])); 1683 delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
1645 slot_buffers.erase(buffer_id); 1684 slot_buffers.erase(buffer_id);
1646 1685
1647 NotifyBufferDeletion();
1648}
1649
1650template <class P>
1651void BufferCache<P>::NotifyBufferDeletion() {
1652 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { 1686 if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
1653 channel_state->dirty_uniform_buffers.fill(~u32{0}); 1687 channel_state->dirty_uniform_buffers.fill(~u32{0});
1654 channel_state->uniform_buffer_binding_sizes.fill({}); 1688 channel_state->uniform_buffer_binding_sizes.fill({});
1655 } 1689 }
1690
1656 auto& flags = maxwell3d->dirty.flags; 1691 auto& flags = maxwell3d->dirty.flags;
1657 flags[Dirty::IndexBuffer] = true; 1692 if (dirty_index) {
1658 flags[Dirty::VertexBuffers] = true; 1693 flags[Dirty::IndexBuffer] = true;
1659 for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { 1694 }
1660 flags[Dirty::VertexBuffer0 + index] = true; 1695
1696 if (dirty_vertex_buffers.size() > 0) {
1697 flags[Dirty::VertexBuffers] = true;
1698 for (auto index : dirty_vertex_buffers) {
1699 flags[Dirty::VertexBuffer0 + index] = true;
1700 }
1661 } 1701 }
1662 channel_state->has_deleted_buffers = true; 1702 channel_state->has_deleted_buffers = true;
1663} 1703}
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index c689fe06b..cf359e241 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -105,6 +105,15 @@ static constexpr Binding NULL_BINDING{
105 .buffer_id = NULL_BUFFER_ID, 105 .buffer_id = NULL_BUFFER_ID,
106}; 106};
107 107
108struct HostBindings {
109 boost::container::small_vector<void*, NUM_VERTEX_BUFFERS> buffers;
110 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets;
111 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes;
112 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
113 u32 min_index{NUM_VERTEX_BUFFERS};
114 u32 max_index{0};
115};
116
108class BufferCacheChannelInfo : public ChannelInfo { 117class BufferCacheChannelInfo : public ChannelInfo {
109public: 118public:
110 BufferCacheChannelInfo() = delete; 119 BufferCacheChannelInfo() = delete;
@@ -173,6 +182,7 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
173 static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS; 182 static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
174 static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS; 183 static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
175 static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS; 184 static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS;
185 static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
176 186
177 static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB; 187 static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB;
178 static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB; 188 static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
@@ -518,8 +528,6 @@ private:
518 528
519 void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false); 529 void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false);
520 530
521 void NotifyBufferDeletion();
522
523 [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index, 531 [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
524 bool is_written) const; 532 bool is_written) const;
525 533
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 2f986097f..62d70e9f3 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -593,6 +593,12 @@ void Maxwell3D::ProcessQueryCondition() {
593} 593}
594 594
595void Maxwell3D::ProcessCounterReset() { 595void Maxwell3D::ProcessCounterReset() {
596#if ANDROID
597 if (!Settings::IsGPULevelHigh()) {
598 // This is problematic on Android, disable on GPU Normal.
599 return;
600 }
601#endif
596 switch (regs.clear_report_value) { 602 switch (regs.clear_report_value) {
597 case Regs::ClearReport::ZPassPixelCount: 603 case Regs::ClearReport::ZPassPixelCount:
598 rasterizer->ResetCounter(QueryType::SamplesPassed); 604 rasterizer->ResetCounter(QueryType::SamplesPassed);
@@ -614,6 +620,12 @@ std::optional<u64> Maxwell3D::GetQueryResult() {
614 case Regs::ReportSemaphore::Report::Payload: 620 case Regs::ReportSemaphore::Report::Payload:
615 return regs.report_semaphore.payload; 621 return regs.report_semaphore.payload;
616 case Regs::ReportSemaphore::Report::ZPassPixelCount64: 622 case Regs::ReportSemaphore::Report::ZPassPixelCount64:
623#if ANDROID
624 if (!Settings::IsGPULevelHigh()) {
625 // This is problematic on Android, disable on GPU Normal.
626 return 120;
627 }
628#endif
617 // Deferred. 629 // Deferred.
618 rasterizer->Query(regs.report_semaphore.Address(), QueryType::SamplesPassed, 630 rasterizer->Query(regs.report_semaphore.Address(), QueryType::SamplesPassed,
619 system.GPU().GetTicks()); 631 system.GPU().GetTicks());
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 295a416a8..456f733cf 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -14,6 +14,7 @@
14#include "core/core.h" 14#include "core/core.h"
15#include "core/core_timing.h" 15#include "core/core_timing.h"
16#include "core/frontend/emu_window.h" 16#include "core/frontend/emu_window.h"
17#include "core/frontend/graphics_context.h"
17#include "core/hle/service/nvdrv/nvdata.h" 18#include "core/hle/service/nvdrv/nvdata.h"
18#include "core/perf_stats.h" 19#include "core/perf_stats.h"
19#include "video_core/cdma_pusher.h" 20#include "video_core/cdma_pusher.h"
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index 3c5317777..889144f38 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -7,7 +7,7 @@
7#include "common/settings.h" 7#include "common/settings.h"
8#include "common/thread.h" 8#include "common/thread.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/frontend/emu_window.h" 10#include "core/frontend/graphics_context.h"
11#include "video_core/control/scheduler.h" 11#include "video_core/control/scheduler.h"
12#include "video_core/dma_pusher.h" 12#include "video_core/dma_pusher.h"
13#include "video_core/gpu.h" 13#include "video_core/gpu.h"
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index e8761a747..2d3f58201 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -5,6 +5,7 @@
5 5
6#include "common/logging/log.h" 6#include "common/logging/log.h"
7#include "core/frontend/emu_window.h" 7#include "core/frontend/emu_window.h"
8#include "core/frontend/graphics_context.h"
8#include "video_core/renderer_base.h" 9#include "video_core/renderer_base.h"
9 10
10namespace VideoCore { 11namespace VideoCore {
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 8d20cbece..3e12a8813 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -9,7 +9,7 @@
9 9
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/frontend/emu_window.h" 12#include "core/frontend/framebuffer_layout.h"
13#include "video_core/gpu.h" 13#include "video_core/gpu.h"
14#include "video_core/rasterizer_interface.h" 14#include "video_core/rasterizer_interface.h"
15 15
@@ -89,6 +89,9 @@ public:
89 void RequestScreenshot(void* data, std::function<void(bool)> callback, 89 void RequestScreenshot(void* data, std::function<void(bool)> callback,
90 const Layout::FramebufferLayout& layout); 90 const Layout::FramebufferLayout& layout);
91 91
92 /// This is called to notify the rendering backend of a surface change
93 virtual void NotifySurfaceChanged() {}
94
92protected: 95protected:
93 Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. 96 Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
94 std::unique_ptr<Core::Frontend::GraphicsContext> context; 97 std::unique_ptr<Core::Frontend::GraphicsContext> context;
diff --git a/src/video_core/renderer_null/renderer_null.cpp b/src/video_core/renderer_null/renderer_null.cpp
index e2a189b63..be92cc2f4 100644
--- a/src/video_core/renderer_null/renderer_null.cpp
+++ b/src/video_core/renderer_null/renderer_null.cpp
@@ -1,6 +1,8 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/frontend/emu_window.h"
5#include "core/frontend/graphics_context.h"
4#include "video_core/renderer_null/renderer_null.h" 6#include "video_core/renderer_null/renderer_null.h"
5 7
6namespace Null { 8namespace Null {
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 6d3bda192..0cc546a3a 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -106,8 +106,10 @@ GLuint Buffer::View(u32 offset, u32 size, PixelFormat format) {
106 return views.back().texture.handle; 106 return views.back().texture.handle;
107} 107}
108 108
109BufferCacheRuntime::BufferCacheRuntime(const Device& device_) 109BufferCacheRuntime::BufferCacheRuntime(const Device& device_,
110 : device{device_}, has_fast_buffer_sub_data{device.HasFastBufferSubData()}, 110 StagingBufferPool& staging_buffer_pool_)
111 : device{device_}, staging_buffer_pool{staging_buffer_pool_},
112 has_fast_buffer_sub_data{device.HasFastBufferSubData()},
111 use_assembly_shaders{device.UseAssemblyShaders()}, 113 use_assembly_shaders{device.UseAssemblyShaders()},
112 has_unified_vertex_buffers{device.HasVertexBufferUnifiedMemory()}, 114 has_unified_vertex_buffers{device.HasVertexBufferUnifiedMemory()},
113 stream_buffer{has_fast_buffer_sub_data ? std::nullopt : std::make_optional<StreamBuffer>()} { 115 stream_buffer{has_fast_buffer_sub_data ? std::nullopt : std::make_optional<StreamBuffer>()} {
@@ -140,6 +142,14 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_)
140 }(); 142 }();
141} 143}
142 144
145StagingBufferMap BufferCacheRuntime::UploadStagingBuffer(size_t size) {
146 return staging_buffer_pool.RequestUploadBuffer(size);
147}
148
149StagingBufferMap BufferCacheRuntime::DownloadStagingBuffer(size_t size) {
150 return staging_buffer_pool.RequestDownloadBuffer(size);
151}
152
143u64 BufferCacheRuntime::GetDeviceMemoryUsage() const { 153u64 BufferCacheRuntime::GetDeviceMemoryUsage() const {
144 if (device.CanReportMemoryUsage()) { 154 if (device.CanReportMemoryUsage()) {
145 return device_access_memory - device.GetCurrentDedicatedVideoMemory(); 155 return device_access_memory - device.GetCurrentDedicatedVideoMemory();
@@ -147,13 +157,47 @@ u64 BufferCacheRuntime::GetDeviceMemoryUsage() const {
147 return 2_GiB; 157 return 2_GiB;
148} 158}
149 159
150void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, 160void BufferCacheRuntime::CopyBuffer(GLuint dst_buffer, GLuint src_buffer,
151 std::span<const VideoCommon::BufferCopy> copies) { 161 std::span<const VideoCommon::BufferCopy> copies, bool barrier) {
162 if (barrier) {
163 PreCopyBarrier();
164 }
152 for (const VideoCommon::BufferCopy& copy : copies) { 165 for (const VideoCommon::BufferCopy& copy : copies) {
153 glCopyNamedBufferSubData( 166 glCopyNamedBufferSubData(src_buffer, dst_buffer, static_cast<GLintptr>(copy.src_offset),
154 src_buffer.Handle(), dst_buffer.Handle(), static_cast<GLintptr>(copy.src_offset), 167 static_cast<GLintptr>(copy.dst_offset),
155 static_cast<GLintptr>(copy.dst_offset), static_cast<GLsizeiptr>(copy.size)); 168 static_cast<GLsizeiptr>(copy.size));
156 } 169 }
170 if (barrier) {
171 PostCopyBarrier();
172 }
173}
174
175void BufferCacheRuntime::CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
176 std::span<const VideoCommon::BufferCopy> copies, bool barrier) {
177 CopyBuffer(dst_buffer, src_buffer.Handle(), copies, barrier);
178}
179
180void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
181 std::span<const VideoCommon::BufferCopy> copies, bool barrier) {
182 CopyBuffer(dst_buffer.Handle(), src_buffer, copies, barrier);
183}
184
185void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
186 std::span<const VideoCommon::BufferCopy> copies) {
187 CopyBuffer(dst_buffer.Handle(), src_buffer.Handle(), copies);
188}
189
190void BufferCacheRuntime::PreCopyBarrier() {
191 // TODO: finer grained barrier?
192 glMemoryBarrier(GL_ALL_BARRIER_BITS);
193}
194
195void BufferCacheRuntime::PostCopyBarrier() {
196 glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT | GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
197}
198
199void BufferCacheRuntime::Finish() {
200 glFinish();
157} 201}
158 202
159void BufferCacheRuntime::ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value) { 203void BufferCacheRuntime::ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value) {
@@ -188,6 +232,15 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset,
188 } 232 }
189} 233}
190 234
235void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) {
236 for (u32 index = 0; index < bindings.buffers.size(); index++) {
237 BindVertexBuffer(
238 bindings.min_index + index, *reinterpret_cast<Buffer*>(bindings.buffers[index]),
239 static_cast<u32>(bindings.offsets[index]), static_cast<u32>(bindings.sizes[index]),
240 static_cast<u32>(bindings.strides[index]));
241 }
242}
243
191void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, 244void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer,
192 u32 offset, u32 size) { 245 u32 offset, u32 size) {
193 if (use_assembly_shaders) { 246 if (use_assembly_shaders) {
@@ -276,6 +329,15 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer,
276 static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); 329 static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
277} 330}
278 331
332void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {
333 for (u32 index = 0; index < bindings.buffers.size(); index++) {
334 glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index,
335 reinterpret_cast<Buffer*>(bindings.buffers[index])->Handle(),
336 static_cast<GLintptr>(bindings.offsets[index]),
337 static_cast<GLsizeiptr>(bindings.sizes[index]));
338 }
339}
340
279void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, 341void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
280 PixelFormat format) { 342 PixelFormat format) {
281 *texture_handles++ = buffer.View(offset, size, format); 343 *texture_handles++ = buffer.View(offset, size, format);
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 18d3c3ac0..e4e000284 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -7,12 +7,12 @@
7#include <span> 7#include <span>
8 8
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/buffer_cache/buffer_cache.h" 10#include "video_core/buffer_cache/buffer_cache_base.h"
11#include "video_core/buffer_cache/memory_tracker_base.h" 11#include "video_core/buffer_cache/memory_tracker_base.h"
12#include "video_core/rasterizer_interface.h" 12#include "video_core/rasterizer_interface.h"
13#include "video_core/renderer_opengl/gl_device.h" 13#include "video_core/renderer_opengl/gl_device.h"
14#include "video_core/renderer_opengl/gl_resource_manager.h" 14#include "video_core/renderer_opengl/gl_resource_manager.h"
15#include "video_core/renderer_opengl/gl_stream_buffer.h" 15#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"
16 16
17namespace OpenGL { 17namespace OpenGL {
18 18
@@ -60,16 +60,34 @@ class BufferCacheRuntime {
60public: 60public:
61 static constexpr u8 INVALID_BINDING = std::numeric_limits<u8>::max(); 61 static constexpr u8 INVALID_BINDING = std::numeric_limits<u8>::max();
62 62
63 explicit BufferCacheRuntime(const Device& device_); 63 explicit BufferCacheRuntime(const Device& device_, StagingBufferPool& staging_buffer_pool_);
64
65 [[nodiscard]] StagingBufferMap UploadStagingBuffer(size_t size);
66
67 [[nodiscard]] StagingBufferMap DownloadStagingBuffer(size_t size);
68
69 void CopyBuffer(GLuint dst_buffer, GLuint src_buffer,
70 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true);
71
72 void CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
73 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true);
74
75 void CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
76 std::span<const VideoCommon::BufferCopy> copies, bool barrier = true);
64 77
65 void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, 78 void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
66 std::span<const VideoCommon::BufferCopy> copies); 79 std::span<const VideoCommon::BufferCopy> copies);
67 80
81 void PreCopyBarrier();
82 void PostCopyBarrier();
83 void Finish();
84
68 void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value); 85 void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value);
69 86
70 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); 87 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
71 88
72 void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); 89 void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
90 void BindVertexBuffers(VideoCommon::HostBindings& bindings);
73 91
74 void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); 92 void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
75 93
@@ -82,6 +100,7 @@ public:
82 bool is_written); 100 bool is_written);
83 101
84 void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); 102 void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size);
103 void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings);
85 104
86 void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, 105 void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
87 VideoCore::Surface::PixelFormat format); 106 VideoCore::Surface::PixelFormat format);
@@ -169,6 +188,7 @@ private:
169 }; 188 };
170 189
171 const Device& device; 190 const Device& device;
191 StagingBufferPool& staging_buffer_pool;
172 192
173 bool has_fast_buffer_sub_data = false; 193 bool has_fast_buffer_sub_data = false;
174 bool use_assembly_shaders = false; 194 bool use_assembly_shaders = false;
@@ -201,7 +221,7 @@ private:
201struct BufferCacheParams { 221struct BufferCacheParams {
202 using Runtime = OpenGL::BufferCacheRuntime; 222 using Runtime = OpenGL::BufferCacheRuntime;
203 using Buffer = OpenGL::Buffer; 223 using Buffer = OpenGL::Buffer;
204 using Async_Buffer = u32; 224 using Async_Buffer = OpenGL::StagingBufferMap;
205 using MemoryTracker = VideoCommon::MemoryTrackerBase<VideoCore::RasterizerInterface>; 225 using MemoryTracker = VideoCommon::MemoryTrackerBase<VideoCore::RasterizerInterface>;
206 226
207 static constexpr bool IS_OPENGL = true; 227 static constexpr bool IS_OPENGL = true;
@@ -209,9 +229,12 @@ struct BufferCacheParams {
209 static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = true; 229 static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = true;
210 static constexpr bool NEEDS_BIND_UNIFORM_INDEX = true; 230 static constexpr bool NEEDS_BIND_UNIFORM_INDEX = true;
211 static constexpr bool NEEDS_BIND_STORAGE_INDEX = true; 231 static constexpr bool NEEDS_BIND_STORAGE_INDEX = true;
212 static constexpr bool USE_MEMORY_MAPS = false; 232 static constexpr bool USE_MEMORY_MAPS = true;
213 static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = true; 233 static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = true;
214 static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = false; 234 static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = false;
235
236 // TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads
237 static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
215}; 238};
216 239
217using BufferCache = VideoCommon::BufferCache<BufferCacheParams>; 240using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 400c21981..03d234f2f 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -201,6 +201,7 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) {
201 use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue() && 201 use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue() &&
202 !(is_amd || (is_intel && !is_linux)) && !strict_context_required; 202 !(is_amd || (is_intel && !is_linux)) && !strict_context_required;
203 use_driver_cache = is_nvidia; 203 use_driver_cache = is_nvidia;
204 supports_conditional_barriers = !is_intel;
204 205
205 LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi); 206 LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi);
206 LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug); 207 LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug);
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index cc0b95f1a..ad27264e5 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -188,6 +188,10 @@ public:
188 return strict_context_required; 188 return strict_context_required;
189 } 189 }
190 190
191 bool SupportsConditionalBarriers() const {
192 return supports_conditional_barriers;
193 }
194
191private: 195private:
192 static bool TestVariableAoffi(); 196 static bool TestVariableAoffi();
193 static bool TestPreciseBug(); 197 static bool TestPreciseBug();
@@ -233,6 +237,7 @@ private:
233 bool has_bool_ref_bug{}; 237 bool has_bool_ref_bug{};
234 bool can_report_memory{}; 238 bool can_report_memory{};
235 bool strict_context_required{}; 239 bool strict_context_required{};
240 bool supports_conditional_barriers{};
236 241
237 std::string vendor_name; 242 std::string vendor_name;
238}; 243};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index f5baa0f3c..fc711c44a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -24,6 +24,7 @@
24#include "video_core/renderer_opengl/gl_query_cache.h" 24#include "video_core/renderer_opengl/gl_query_cache.h"
25#include "video_core/renderer_opengl/gl_rasterizer.h" 25#include "video_core/renderer_opengl/gl_rasterizer.h"
26#include "video_core/renderer_opengl/gl_shader_cache.h" 26#include "video_core/renderer_opengl/gl_shader_cache.h"
27#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"
27#include "video_core/renderer_opengl/gl_texture_cache.h" 28#include "video_core/renderer_opengl/gl_texture_cache.h"
28#include "video_core/renderer_opengl/maxwell_to_gl.h" 29#include "video_core/renderer_opengl/maxwell_to_gl.h"
29#include "video_core/renderer_opengl/renderer_opengl.h" 30#include "video_core/renderer_opengl/renderer_opengl.h"
@@ -58,8 +59,9 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra
58 StateTracker& state_tracker_) 59 StateTracker& state_tracker_)
59 : RasterizerAccelerated(cpu_memory_), gpu(gpu_), device(device_), screen_info(screen_info_), 60 : RasterizerAccelerated(cpu_memory_), gpu(gpu_), device(device_), screen_info(screen_info_),
60 program_manager(program_manager_), state_tracker(state_tracker_), 61 program_manager(program_manager_), state_tracker(state_tracker_),
61 texture_cache_runtime(device, program_manager, state_tracker), 62 texture_cache_runtime(device, program_manager, state_tracker, staging_buffer_pool),
62 texture_cache(texture_cache_runtime, *this), buffer_cache_runtime(device), 63 texture_cache(texture_cache_runtime, *this),
64 buffer_cache_runtime(device, staging_buffer_pool),
63 buffer_cache(*this, cpu_memory_, buffer_cache_runtime), 65 buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
64 shader_cache(*this, emu_window_, device, texture_cache, buffer_cache, program_manager, 66 shader_cache(*this, emu_window_, device, texture_cache, buffer_cache, program_manager,
65 state_tracker, gpu.ShaderNotify()), 67 state_tracker, gpu.ShaderNotify()),
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 410d8ffc5..a73ad15c1 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -230,6 +230,7 @@ private:
230 ProgramManager& program_manager; 230 ProgramManager& program_manager;
231 StateTracker& state_tracker; 231 StateTracker& state_tracker;
232 232
233 StagingBufferPool staging_buffer_pool;
233 TextureCacheRuntime texture_cache_runtime; 234 TextureCacheRuntime texture_cache_runtime;
234 TextureCache texture_cache; 235 TextureCache texture_cache;
235 BufferCacheRuntime buffer_cache_runtime; 236 BufferCacheRuntime buffer_cache_runtime;
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 6ecda2984..3f077311e 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -232,12 +232,14 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
232 .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(), 232 .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
233 }, 233 },
234 host_info{ 234 host_info{
235 .support_float64 = true,
235 .support_float16 = false, 236 .support_float16 = false,
236 .support_int64 = device.HasShaderInt64(), 237 .support_int64 = device.HasShaderInt64(),
237 .needs_demote_reorder = device.IsAmd(), 238 .needs_demote_reorder = device.IsAmd(),
238 .support_snorm_render_buffer = false, 239 .support_snorm_render_buffer = false,
239 .support_viewport_index_layer = device.HasVertexViewportLayer(), 240 .support_viewport_index_layer = device.HasVertexViewportLayer(),
240 .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(), 241 .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
242 .support_conditional_barrier = device.SupportsConditionalBarriers(),
241 } { 243 } {
242 if (use_asynchronous_shaders) { 244 if (use_asynchronous_shaders) {
243 workers = CreateWorkers(); 245 workers = CreateWorkers();
diff --git a/src/video_core/renderer_opengl/gl_shader_context.h b/src/video_core/renderer_opengl/gl_shader_context.h
index ca2bd8e8e..207a75d42 100644
--- a/src/video_core/renderer_opengl/gl_shader_context.h
+++ b/src/video_core/renderer_opengl/gl_shader_context.h
@@ -4,6 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include "core/frontend/emu_window.h" 6#include "core/frontend/emu_window.h"
7#include "core/frontend/graphics_context.h"
7#include "shader_recompiler/frontend/ir/basic_block.h" 8#include "shader_recompiler/frontend/ir/basic_block.h"
8#include "shader_recompiler/frontend/maxwell/control_flow.h" 9#include "shader_recompiler/frontend/maxwell/control_flow.h"
9 10
diff --git a/src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp b/src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp
new file mode 100644
index 000000000..bbb06e51f
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp
@@ -0,0 +1,150 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <memory>
6#include <span>
7
8#include <glad/glad.h>
9
10#include "common/alignment.h"
11#include "common/assert.h"
12#include "common/bit_util.h"
13#include "common/microprofile.h"
14#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"
15
16MICROPROFILE_DEFINE(OpenGL_BufferRequest, "OpenGL", "BufferRequest", MP_RGB(128, 128, 192));
17
18namespace OpenGL {
19
20StagingBufferMap::~StagingBufferMap() {
21 if (sync) {
22 sync->Create();
23 }
24}
25
26StagingBuffers::StagingBuffers(GLenum storage_flags_, GLenum map_flags_)
27 : storage_flags{storage_flags_}, map_flags{map_flags_} {}
28
29StagingBuffers::~StagingBuffers() = default;
30
31StagingBufferMap StagingBuffers::RequestMap(size_t requested_size, bool insert_fence) {
32 MICROPROFILE_SCOPE(OpenGL_BufferRequest);
33
34 const size_t index = RequestBuffer(requested_size);
35 OGLSync* const sync = insert_fence ? &syncs[index] : nullptr;
36 sync_indices[index] = insert_fence ? ++current_sync_index : 0;
37 return StagingBufferMap{
38 .mapped_span = std::span(maps[index], requested_size),
39 .sync = sync,
40 .buffer = buffers[index].handle,
41 };
42}
43
44size_t StagingBuffers::RequestBuffer(size_t requested_size) {
45 if (const std::optional<size_t> index = FindBuffer(requested_size); index) {
46 return *index;
47 }
48
49 OGLBuffer& buffer = buffers.emplace_back();
50 buffer.Create();
51 const auto next_pow2_size = Common::NextPow2(requested_size);
52 glNamedBufferStorage(buffer.handle, next_pow2_size, nullptr,
53 storage_flags | GL_MAP_PERSISTENT_BIT);
54 maps.push_back(static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, next_pow2_size,
55 map_flags | GL_MAP_PERSISTENT_BIT)));
56 syncs.emplace_back();
57 sync_indices.emplace_back();
58 sizes.push_back(next_pow2_size);
59
60 ASSERT(syncs.size() == buffers.size() && buffers.size() == maps.size() &&
61 maps.size() == sizes.size());
62
63 return buffers.size() - 1;
64}
65
66std::optional<size_t> StagingBuffers::FindBuffer(size_t requested_size) {
67 size_t known_unsignaled_index = current_sync_index + 1;
68 size_t smallest_buffer = std::numeric_limits<size_t>::max();
69 std::optional<size_t> found;
70 const size_t num_buffers = sizes.size();
71 for (size_t index = 0; index < num_buffers; ++index) {
72 const size_t buffer_size = sizes[index];
73 if (buffer_size < requested_size || buffer_size >= smallest_buffer) {
74 continue;
75 }
76 if (syncs[index].handle != 0) {
77 if (sync_indices[index] >= known_unsignaled_index) {
78 // This fence is later than a fence that is known to not be signaled
79 continue;
80 }
81 if (!syncs[index].IsSignaled()) {
82 // Since this fence hasn't been signaled, it's safe to assume all later
83 // fences haven't been signaled either
84 known_unsignaled_index = std::min(known_unsignaled_index, sync_indices[index]);
85 continue;
86 }
87 syncs[index].Release();
88 }
89 smallest_buffer = buffer_size;
90 found = index;
91 }
92 return found;
93}
94
95StreamBuffer::StreamBuffer() {
96 static constexpr GLenum flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
97 buffer.Create();
98 glObjectLabel(GL_BUFFER, buffer.handle, -1, "Stream Buffer");
99 glNamedBufferStorage(buffer.handle, STREAM_BUFFER_SIZE, nullptr, flags);
100 mapped_pointer =
101 static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, STREAM_BUFFER_SIZE, flags));
102 for (OGLSync& sync : fences) {
103 sync.Create();
104 }
105}
106
107std::pair<std::span<u8>, size_t> StreamBuffer::Request(size_t size) noexcept {
108 ASSERT(size < REGION_SIZE);
109 for (size_t region = Region(used_iterator), region_end = Region(iterator); region < region_end;
110 ++region) {
111 fences[region].Create();
112 }
113 used_iterator = iterator;
114
115 for (size_t region = Region(free_iterator) + 1,
116 region_end = std::min(Region(iterator + size) + 1, NUM_SYNCS);
117 region < region_end; ++region) {
118 glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED);
119 fences[region].Release();
120 }
121 if (iterator + size >= free_iterator) {
122 free_iterator = iterator + size;
123 }
124 if (iterator + size > STREAM_BUFFER_SIZE) {
125 for (size_t region = Region(used_iterator); region < NUM_SYNCS; ++region) {
126 fences[region].Create();
127 }
128 used_iterator = 0;
129 iterator = 0;
130 free_iterator = size;
131
132 for (size_t region = 0, region_end = Region(size); region <= region_end; ++region) {
133 glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED);
134 fences[region].Release();
135 }
136 }
137 const size_t offset = iterator;
138 iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT);
139 return {std::span(mapped_pointer + offset, size), offset};
140}
141
142StagingBufferMap StagingBufferPool::RequestUploadBuffer(size_t size) {
143 return upload_buffers.RequestMap(size, true);
144}
145
146StagingBufferMap StagingBufferPool::RequestDownloadBuffer(size_t size) {
147 return download_buffers.RequestMap(size, false);
148}
149
150} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_staging_buffer_pool.h
index 8fe927aaf..60f72d3a0 100644
--- a/src/video_core/renderer_opengl/gl_stream_buffer.h
+++ b/src/video_core/renderer_opengl/gl_staging_buffer_pool.h
@@ -4,8 +4,10 @@
4#pragma once 4#pragma once
5 5
6#include <array> 6#include <array>
7#include <optional>
7#include <span> 8#include <span>
8#include <utility> 9#include <utility>
10#include <vector>
9 11
10#include <glad/glad.h> 12#include <glad/glad.h>
11 13
@@ -17,6 +19,35 @@ namespace OpenGL {
17 19
18using namespace Common::Literals; 20using namespace Common::Literals;
19 21
22struct StagingBufferMap {
23 ~StagingBufferMap();
24
25 std::span<u8> mapped_span;
26 size_t offset = 0;
27 OGLSync* sync;
28 GLuint buffer;
29};
30
31struct StagingBuffers {
32 explicit StagingBuffers(GLenum storage_flags_, GLenum map_flags_);
33 ~StagingBuffers();
34
35 StagingBufferMap RequestMap(size_t requested_size, bool insert_fence);
36
37 size_t RequestBuffer(size_t requested_size);
38
39 std::optional<size_t> FindBuffer(size_t requested_size);
40
41 std::vector<OGLSync> syncs;
42 std::vector<OGLBuffer> buffers;
43 std::vector<u8*> maps;
44 std::vector<size_t> sizes;
45 std::vector<size_t> sync_indices;
46 GLenum storage_flags;
47 GLenum map_flags;
48 size_t current_sync_index = 0;
49};
50
20class StreamBuffer { 51class StreamBuffer {
21 static constexpr size_t STREAM_BUFFER_SIZE = 64_MiB; 52 static constexpr size_t STREAM_BUFFER_SIZE = 64_MiB;
22 static constexpr size_t NUM_SYNCS = 16; 53 static constexpr size_t NUM_SYNCS = 16;
@@ -48,4 +79,17 @@ private:
48 std::array<OGLSync, NUM_SYNCS> fences; 79 std::array<OGLSync, NUM_SYNCS> fences;
49}; 80};
50 81
82class StagingBufferPool {
83public:
84 StagingBufferPool() = default;
85 ~StagingBufferPool() = default;
86
87 StagingBufferMap RequestUploadBuffer(size_t size);
88 StagingBufferMap RequestDownloadBuffer(size_t size);
89
90private:
91 StagingBuffers upload_buffers{GL_MAP_WRITE_BIT, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT};
92 StagingBuffers download_buffers{GL_MAP_READ_BIT | GL_CLIENT_STORAGE_BIT, GL_MAP_READ_BIT};
93};
94
51} // namespace OpenGL 95} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
deleted file mode 100644
index 2005c8993..000000000
--- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <memory>
6#include <span>
7
8#include <glad/glad.h>
9
10#include "common/alignment.h"
11#include "common/assert.h"
12#include "video_core/renderer_opengl/gl_stream_buffer.h"
13
14namespace OpenGL {
15
16StreamBuffer::StreamBuffer() {
17 static constexpr GLenum flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
18 buffer.Create();
19 glObjectLabel(GL_BUFFER, buffer.handle, -1, "Stream Buffer");
20 glNamedBufferStorage(buffer.handle, STREAM_BUFFER_SIZE, nullptr, flags);
21 mapped_pointer =
22 static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, STREAM_BUFFER_SIZE, flags));
23 for (OGLSync& sync : fences) {
24 sync.Create();
25 }
26}
27
28std::pair<std::span<u8>, size_t> StreamBuffer::Request(size_t size) noexcept {
29 ASSERT(size < REGION_SIZE);
30 for (size_t region = Region(used_iterator), region_end = Region(iterator); region < region_end;
31 ++region) {
32 fences[region].Create();
33 }
34 used_iterator = iterator;
35
36 for (size_t region = Region(free_iterator) + 1,
37 region_end = std::min(Region(iterator + size) + 1, NUM_SYNCS);
38 region < region_end; ++region) {
39 glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED);
40 fences[region].Release();
41 }
42 if (iterator + size >= free_iterator) {
43 free_iterator = iterator + size;
44 }
45 if (iterator + size > STREAM_BUFFER_SIZE) {
46 for (size_t region = Region(used_iterator); region < NUM_SYNCS; ++region) {
47 fences[region].Create();
48 }
49 used_iterator = 0;
50 iterator = 0;
51 free_iterator = size;
52
53 for (size_t region = 0, region_end = Region(size); region <= region_end; ++region) {
54 glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED);
55 fences[region].Release();
56 }
57 }
58 const size_t offset = iterator;
59 iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT);
60 return {std::span(mapped_pointer + offset, size), offset};
61}
62
63} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 56d0ff869..1c5dbcdd8 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -456,19 +456,14 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form
456 return is_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8; 456 return is_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8;
457 } 457 }
458} 458}
459
460} // Anonymous namespace 459} // Anonymous namespace
461 460
462ImageBufferMap::~ImageBufferMap() {
463 if (sync) {
464 sync->Create();
465 }
466}
467
468TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager& program_manager, 461TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager& program_manager,
469 StateTracker& state_tracker_) 462 StateTracker& state_tracker_,
470 : device{device_}, state_tracker{state_tracker_}, util_shaders(program_manager), 463 StagingBufferPool& staging_buffer_pool_)
471 format_conversion_pass{util_shaders}, resolution{Settings::values.resolution_info} { 464 : device{device_}, state_tracker{state_tracker_}, staging_buffer_pool{staging_buffer_pool_},
465 util_shaders(program_manager), format_conversion_pass{util_shaders},
466 resolution{Settings::values.resolution_info} {
472 static constexpr std::array TARGETS{GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D}; 467 static constexpr std::array TARGETS{GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D};
473 for (size_t i = 0; i < TARGETS.size(); ++i) { 468 for (size_t i = 0; i < TARGETS.size(); ++i) {
474 const GLenum target = TARGETS[i]; 469 const GLenum target = TARGETS[i];
@@ -558,12 +553,12 @@ void TextureCacheRuntime::Finish() {
558 glFinish(); 553 glFinish();
559} 554}
560 555
561ImageBufferMap TextureCacheRuntime::UploadStagingBuffer(size_t size) { 556StagingBufferMap TextureCacheRuntime::UploadStagingBuffer(size_t size) {
562 return upload_buffers.RequestMap(size, true); 557 return staging_buffer_pool.RequestUploadBuffer(size);
563} 558}
564 559
565ImageBufferMap TextureCacheRuntime::DownloadStagingBuffer(size_t size) { 560StagingBufferMap TextureCacheRuntime::DownloadStagingBuffer(size_t size) {
566 return download_buffers.RequestMap(size, false); 561 return staging_buffer_pool.RequestDownloadBuffer(size);
567} 562}
568 563
569u64 TextureCacheRuntime::GetDeviceMemoryUsage() const { 564u64 TextureCacheRuntime::GetDeviceMemoryUsage() const {
@@ -648,7 +643,7 @@ void TextureCacheRuntime::BlitFramebuffer(Framebuffer* dst, Framebuffer* src,
648 is_linear ? GL_LINEAR : GL_NEAREST); 643 is_linear ? GL_LINEAR : GL_NEAREST);
649} 644}
650 645
651void TextureCacheRuntime::AccelerateImageUpload(Image& image, const ImageBufferMap& map, 646void TextureCacheRuntime::AccelerateImageUpload(Image& image, const StagingBufferMap& map,
652 std::span<const SwizzleParameters> swizzles) { 647 std::span<const SwizzleParameters> swizzles) {
653 switch (image.info.type) { 648 switch (image.info.type) {
654 case ImageType::e2D: 649 case ImageType::e2D:
@@ -690,64 +685,6 @@ bool TextureCacheRuntime::HasNativeASTC() const noexcept {
690 return device.HasASTC(); 685 return device.HasASTC();
691} 686}
692 687
693TextureCacheRuntime::StagingBuffers::StagingBuffers(GLenum storage_flags_, GLenum map_flags_)
694 : storage_flags{storage_flags_}, map_flags{map_flags_} {}
695
696TextureCacheRuntime::StagingBuffers::~StagingBuffers() = default;
697
698ImageBufferMap TextureCacheRuntime::StagingBuffers::RequestMap(size_t requested_size,
699 bool insert_fence) {
700 const size_t index = RequestBuffer(requested_size);
701 OGLSync* const sync = insert_fence ? &syncs[index] : nullptr;
702 return ImageBufferMap{
703 .mapped_span = std::span(maps[index], requested_size),
704 .sync = sync,
705 .buffer = buffers[index].handle,
706 };
707}
708
709size_t TextureCacheRuntime::StagingBuffers::RequestBuffer(size_t requested_size) {
710 if (const std::optional<size_t> index = FindBuffer(requested_size); index) {
711 return *index;
712 }
713
714 OGLBuffer& buffer = buffers.emplace_back();
715 buffer.Create();
716 glNamedBufferStorage(buffer.handle, requested_size, nullptr,
717 storage_flags | GL_MAP_PERSISTENT_BIT);
718 maps.push_back(static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, requested_size,
719 map_flags | GL_MAP_PERSISTENT_BIT)));
720
721 syncs.emplace_back();
722 sizes.push_back(requested_size);
723
724 ASSERT(syncs.size() == buffers.size() && buffers.size() == maps.size() &&
725 maps.size() == sizes.size());
726
727 return buffers.size() - 1;
728}
729
730std::optional<size_t> TextureCacheRuntime::StagingBuffers::FindBuffer(size_t requested_size) {
731 size_t smallest_buffer = std::numeric_limits<size_t>::max();
732 std::optional<size_t> found;
733 const size_t num_buffers = sizes.size();
734 for (size_t index = 0; index < num_buffers; ++index) {
735 const size_t buffer_size = sizes[index];
736 if (buffer_size < requested_size || buffer_size >= smallest_buffer) {
737 continue;
738 }
739 if (syncs[index].handle != 0) {
740 if (!syncs[index].IsSignaled()) {
741 continue;
742 }
743 syncs[index].Release();
744 }
745 smallest_buffer = buffer_size;
746 found = index;
747 }
748 return found;
749}
750
751Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_, GPUVAddr gpu_addr_, 688Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_, GPUVAddr gpu_addr_,
752 VAddr cpu_addr_) 689 VAddr cpu_addr_)
753 : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), runtime{&runtime_} { 690 : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), runtime{&runtime_} {
@@ -823,7 +760,7 @@ void Image::UploadMemory(GLuint buffer_handle, size_t buffer_offset,
823 } 760 }
824} 761}
825 762
826void Image::UploadMemory(const ImageBufferMap& map, 763void Image::UploadMemory(const StagingBufferMap& map,
827 std::span<const VideoCommon::BufferImageCopy> copies) { 764 std::span<const VideoCommon::BufferImageCopy> copies) {
828 UploadMemory(map.buffer, map.offset, copies); 765 UploadMemory(map.buffer, map.offset, copies);
829} 766}
@@ -870,7 +807,7 @@ void Image::DownloadMemory(std::span<GLuint> buffer_handles, std::span<size_t> b
870 } 807 }
871} 808}
872 809
873void Image::DownloadMemory(ImageBufferMap& map, 810void Image::DownloadMemory(StagingBufferMap& map,
874 std::span<const VideoCommon::BufferImageCopy> copies) { 811 std::span<const VideoCommon::BufferImageCopy> copies) {
875 DownloadMemory(map.buffer, map.offset, copies); 812 DownloadMemory(map.buffer, map.offset, copies);
876} 813}
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 3e9b3302b..1148b73d7 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -11,6 +11,7 @@
11#include "shader_recompiler/shader_info.h" 11#include "shader_recompiler/shader_info.h"
12#include "video_core/renderer_opengl/gl_device.h" 12#include "video_core/renderer_opengl/gl_device.h"
13#include "video_core/renderer_opengl/gl_resource_manager.h" 13#include "video_core/renderer_opengl/gl_resource_manager.h"
14#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"
14#include "video_core/renderer_opengl/util_shaders.h" 15#include "video_core/renderer_opengl/util_shaders.h"
15#include "video_core/texture_cache/image_view_base.h" 16#include "video_core/texture_cache/image_view_base.h"
16#include "video_core/texture_cache/texture_cache_base.h" 17#include "video_core/texture_cache/texture_cache_base.h"
@@ -37,15 +38,6 @@ using VideoCommon::Region2D;
37using VideoCommon::RenderTargets; 38using VideoCommon::RenderTargets;
38using VideoCommon::SlotVector; 39using VideoCommon::SlotVector;
39 40
40struct ImageBufferMap {
41 ~ImageBufferMap();
42
43 std::span<u8> mapped_span;
44 size_t offset = 0;
45 OGLSync* sync;
46 GLuint buffer;
47};
48
49struct FormatProperties { 41struct FormatProperties {
50 GLenum compatibility_class; 42 GLenum compatibility_class;
51 bool compatibility_by_size; 43 bool compatibility_by_size;
@@ -74,14 +66,15 @@ class TextureCacheRuntime {
74 66
75public: 67public:
76 explicit TextureCacheRuntime(const Device& device, ProgramManager& program_manager, 68 explicit TextureCacheRuntime(const Device& device, ProgramManager& program_manager,
77 StateTracker& state_tracker); 69 StateTracker& state_tracker,
70 StagingBufferPool& staging_buffer_pool);
78 ~TextureCacheRuntime(); 71 ~TextureCacheRuntime();
79 72
80 void Finish(); 73 void Finish();
81 74
82 ImageBufferMap UploadStagingBuffer(size_t size); 75 StagingBufferMap UploadStagingBuffer(size_t size);
83 76
84 ImageBufferMap DownloadStagingBuffer(size_t size); 77 StagingBufferMap DownloadStagingBuffer(size_t size);
85 78
86 u64 GetDeviceLocalMemory() const { 79 u64 GetDeviceLocalMemory() const {
87 return device_access_memory; 80 return device_access_memory;
@@ -120,7 +113,7 @@ public:
120 const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter, 113 const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter,
121 Tegra::Engines::Fermi2D::Operation operation); 114 Tegra::Engines::Fermi2D::Operation operation);
122 115
123 void AccelerateImageUpload(Image& image, const ImageBufferMap& map, 116 void AccelerateImageUpload(Image& image, const StagingBufferMap& map,
124 std::span<const VideoCommon::SwizzleParameters> swizzles); 117 std::span<const VideoCommon::SwizzleParameters> swizzles);
125 118
126 void InsertUploadMemoryBarrier(); 119 void InsertUploadMemoryBarrier();
@@ -149,35 +142,16 @@ public:
149 } 142 }
150 143
151private: 144private:
152 struct StagingBuffers {
153 explicit StagingBuffers(GLenum storage_flags_, GLenum map_flags_);
154 ~StagingBuffers();
155
156 ImageBufferMap RequestMap(size_t requested_size, bool insert_fence);
157
158 size_t RequestBuffer(size_t requested_size);
159
160 std::optional<size_t> FindBuffer(size_t requested_size);
161
162 std::vector<OGLSync> syncs;
163 std::vector<OGLBuffer> buffers;
164 std::vector<u8*> maps;
165 std::vector<size_t> sizes;
166 GLenum storage_flags;
167 GLenum map_flags;
168 };
169
170 const Device& device; 145 const Device& device;
171 StateTracker& state_tracker; 146 StateTracker& state_tracker;
147 StagingBufferPool& staging_buffer_pool;
148
172 UtilShaders util_shaders; 149 UtilShaders util_shaders;
173 FormatConversionPass format_conversion_pass; 150 FormatConversionPass format_conversion_pass;
174 151
175 std::array<std::unordered_map<GLenum, FormatProperties>, 3> format_properties; 152 std::array<std::unordered_map<GLenum, FormatProperties>, 3> format_properties;
176 bool has_broken_texture_view_formats = false; 153 bool has_broken_texture_view_formats = false;
177 154
178 StagingBuffers upload_buffers{GL_MAP_WRITE_BIT, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT};
179 StagingBuffers download_buffers{GL_MAP_READ_BIT | GL_CLIENT_STORAGE_BIT, GL_MAP_READ_BIT};
180
181 OGLTexture null_image_1d_array; 155 OGLTexture null_image_1d_array;
182 OGLTexture null_image_cube_array; 156 OGLTexture null_image_cube_array;
183 OGLTexture null_image_3d; 157 OGLTexture null_image_3d;
@@ -213,7 +187,7 @@ public:
213 void UploadMemory(GLuint buffer_handle, size_t buffer_offset, 187 void UploadMemory(GLuint buffer_handle, size_t buffer_offset,
214 std::span<const VideoCommon::BufferImageCopy> copies); 188 std::span<const VideoCommon::BufferImageCopy> copies);
215 189
216 void UploadMemory(const ImageBufferMap& map, 190 void UploadMemory(const StagingBufferMap& map,
217 std::span<const VideoCommon::BufferImageCopy> copies); 191 std::span<const VideoCommon::BufferImageCopy> copies);
218 192
219 void DownloadMemory(GLuint buffer_handle, size_t buffer_offset, 193 void DownloadMemory(GLuint buffer_handle, size_t buffer_offset,
@@ -222,7 +196,8 @@ public:
222 void DownloadMemory(std::span<GLuint> buffer_handle, std::span<size_t> buffer_offset, 196 void DownloadMemory(std::span<GLuint> buffer_handle, std::span<size_t> buffer_offset,
223 std::span<const VideoCommon::BufferImageCopy> copies); 197 std::span<const VideoCommon::BufferImageCopy> copies);
224 198
225 void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies); 199 void DownloadMemory(StagingBufferMap& map,
200 std::span<const VideoCommon::BufferImageCopy> copies);
226 201
227 GLuint StorageHandle() noexcept; 202 GLuint StorageHandle() noexcept;
228 203
diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp
index 2c7ac210b..544982d18 100644
--- a/src/video_core/renderer_opengl/util_shaders.cpp
+++ b/src/video_core/renderer_opengl/util_shaders.cpp
@@ -19,6 +19,7 @@
19#include "video_core/host_shaders/pitch_unswizzle_comp.h" 19#include "video_core/host_shaders/pitch_unswizzle_comp.h"
20#include "video_core/renderer_opengl/gl_shader_manager.h" 20#include "video_core/renderer_opengl/gl_shader_manager.h"
21#include "video_core/renderer_opengl/gl_shader_util.h" 21#include "video_core/renderer_opengl/gl_shader_util.h"
22#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"
22#include "video_core/renderer_opengl/gl_texture_cache.h" 23#include "video_core/renderer_opengl/gl_texture_cache.h"
23#include "video_core/renderer_opengl/util_shaders.h" 24#include "video_core/renderer_opengl/util_shaders.h"
24#include "video_core/texture_cache/accelerated_swizzle.h" 25#include "video_core/texture_cache/accelerated_swizzle.h"
@@ -63,7 +64,7 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_)
63 64
64UtilShaders::~UtilShaders() = default; 65UtilShaders::~UtilShaders() = default;
65 66
66void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map, 67void UtilShaders::ASTCDecode(Image& image, const StagingBufferMap& map,
67 std::span<const VideoCommon::SwizzleParameters> swizzles) { 68 std::span<const VideoCommon::SwizzleParameters> swizzles) {
68 static constexpr GLuint BINDING_INPUT_BUFFER = 0; 69 static constexpr GLuint BINDING_INPUT_BUFFER = 0;
69 static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; 70 static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
@@ -111,7 +112,7 @@ void UtilShaders::ASTCDecode(Image& image, const ImageBufferMap& map,
111 program_manager.RestoreGuestCompute(); 112 program_manager.RestoreGuestCompute();
112} 113}
113 114
114void UtilShaders::BlockLinearUpload2D(Image& image, const ImageBufferMap& map, 115void UtilShaders::BlockLinearUpload2D(Image& image, const StagingBufferMap& map,
115 std::span<const SwizzleParameters> swizzles) { 116 std::span<const SwizzleParameters> swizzles) {
116 static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1}; 117 static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1};
117 static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0; 118 static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0;
@@ -148,7 +149,7 @@ void UtilShaders::BlockLinearUpload2D(Image& image, const ImageBufferMap& map,
148 program_manager.RestoreGuestCompute(); 149 program_manager.RestoreGuestCompute();
149} 150}
150 151
151void UtilShaders::BlockLinearUpload3D(Image& image, const ImageBufferMap& map, 152void UtilShaders::BlockLinearUpload3D(Image& image, const StagingBufferMap& map,
152 std::span<const SwizzleParameters> swizzles) { 153 std::span<const SwizzleParameters> swizzles) {
153 static constexpr Extent3D WORKGROUP_SIZE{16, 8, 8}; 154 static constexpr Extent3D WORKGROUP_SIZE{16, 8, 8};
154 155
@@ -189,7 +190,7 @@ void UtilShaders::BlockLinearUpload3D(Image& image, const ImageBufferMap& map,
189 program_manager.RestoreGuestCompute(); 190 program_manager.RestoreGuestCompute();
190} 191}
191 192
192void UtilShaders::PitchUpload(Image& image, const ImageBufferMap& map, 193void UtilShaders::PitchUpload(Image& image, const StagingBufferMap& map,
193 std::span<const SwizzleParameters> swizzles) { 194 std::span<const SwizzleParameters> swizzles) {
194 static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1}; 195 static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1};
195 static constexpr GLuint BINDING_INPUT_BUFFER = 0; 196 static constexpr GLuint BINDING_INPUT_BUFFER = 0;
diff --git a/src/video_core/renderer_opengl/util_shaders.h b/src/video_core/renderer_opengl/util_shaders.h
index 9013808e7..feecd404c 100644
--- a/src/video_core/renderer_opengl/util_shaders.h
+++ b/src/video_core/renderer_opengl/util_shaders.h
@@ -16,23 +16,23 @@ namespace OpenGL {
16class Image; 16class Image;
17class ProgramManager; 17class ProgramManager;
18 18
19struct ImageBufferMap; 19struct StagingBufferMap;
20 20
21class UtilShaders { 21class UtilShaders {
22public: 22public:
23 explicit UtilShaders(ProgramManager& program_manager); 23 explicit UtilShaders(ProgramManager& program_manager);
24 ~UtilShaders(); 24 ~UtilShaders();
25 25
26 void ASTCDecode(Image& image, const ImageBufferMap& map, 26 void ASTCDecode(Image& image, const StagingBufferMap& map,
27 std::span<const VideoCommon::SwizzleParameters> swizzles); 27 std::span<const VideoCommon::SwizzleParameters> swizzles);
28 28
29 void BlockLinearUpload2D(Image& image, const ImageBufferMap& map, 29 void BlockLinearUpload2D(Image& image, const StagingBufferMap& map,
30 std::span<const VideoCommon::SwizzleParameters> swizzles); 30 std::span<const VideoCommon::SwizzleParameters> swizzles);
31 31
32 void BlockLinearUpload3D(Image& image, const ImageBufferMap& map, 32 void BlockLinearUpload3D(Image& image, const StagingBufferMap& map,
33 std::span<const VideoCommon::SwizzleParameters> swizzles); 33 std::span<const VideoCommon::SwizzleParameters> swizzles);
34 34
35 void PitchUpload(Image& image, const ImageBufferMap& map, 35 void PitchUpload(Image& image, const StagingBufferMap& map,
36 std::span<const VideoCommon::SwizzleParameters> swizzles); 36 std::span<const VideoCommon::SwizzleParameters> swizzles);
37 37
38 void CopyBC4(Image& dst_image, Image& src_image, 38 void CopyBC4(Image& dst_image, Image& src_image,
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index b75d7220d..9a0b10568 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -347,6 +347,14 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
347 347
348VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type, 348VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
349 Maxwell::VertexAttribute::Size size) { 349 Maxwell::VertexAttribute::Size size) {
350 if (device.MustEmulateScaledFormats()) {
351 if (type == Maxwell::VertexAttribute::Type::SScaled) {
352 type = Maxwell::VertexAttribute::Type::SInt;
353 } else if (type == Maxwell::VertexAttribute::Type::UScaled) {
354 type = Maxwell::VertexAttribute::Type::UInt;
355 }
356 }
357
350 const VkFormat format{([&]() { 358 const VkFormat format{([&]() {
351 switch (type) { 359 switch (type) {
352 case Maxwell::VertexAttribute::Type::UnusedEnumDoNotUseBecauseItWillGoAway: 360 case Maxwell::VertexAttribute::Type::UnusedEnumDoNotUseBecauseItWillGoAway:
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 8e31eba34..77128c6e2 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -16,7 +16,7 @@
16#include "common/settings.h" 16#include "common/settings.h"
17#include "common/telemetry.h" 17#include "common/telemetry.h"
18#include "core/core_timing.h" 18#include "core/core_timing.h"
19#include "core/frontend/emu_window.h" 19#include "core/frontend/graphics_context.h"
20#include "core/telemetry_session.h" 20#include "core/telemetry_session.h"
21#include "video_core/gpu.h" 21#include "video_core/gpu.h"
22#include "video_core/renderer_vulkan/renderer_vulkan.h" 22#include "video_core/renderer_vulkan/renderer_vulkan.h"
@@ -84,8 +84,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
84 Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, 84 Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
85 std::unique_ptr<Core::Frontend::GraphicsContext> context_) try 85 std::unique_ptr<Core::Frontend::GraphicsContext> context_) try
86 : RendererBase(emu_window, std::move(context_)), telemetry_session(telemetry_session_), 86 : RendererBase(emu_window, std::move(context_)), telemetry_session(telemetry_session_),
87 cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary()), 87 cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())),
88 instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, 88 instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
89 Settings::values.renderer_debug.GetValue())), 89 Settings::values.renderer_debug.GetValue())),
90 debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr), 90 debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr),
91 surface(CreateSurface(instance, render_window.GetWindowInfo())), 91 surface(CreateSurface(instance, render_window.GetWindowInfo())),
@@ -93,7 +93,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
93 state_tracker(), scheduler(device, state_tracker), 93 state_tracker(), scheduler(device, state_tracker),
94 swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width, 94 swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,
95 render_window.GetFramebufferLayout().height, false), 95 render_window.GetFramebufferLayout().height, false),
96 present_manager(render_window, device, memory_allocator, scheduler, swapchain), 96 present_manager(instance, render_window, device, memory_allocator, scheduler, swapchain,
97 surface),
97 blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, present_manager, 98 blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, present_manager,
98 scheduler, screen_info), 99 scheduler, screen_info),
99 rasterizer(render_window, gpu, cpu_memory, screen_info, device, memory_allocator, 100 rasterizer(render_window, gpu, cpu_memory, screen_info, device, memory_allocator,
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index f44367cb2..b2e8cbd1b 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -54,6 +54,10 @@ public:
54 return device.GetDriverName(); 54 return device.GetDriverName();
55 } 55 }
56 56
57 void NotifySurfaceChanged() override {
58 present_manager.NotifySurfaceChanged();
59 }
60
57private: 61private:
58 void Report() const; 62 void Report() const;
59 63
@@ -63,7 +67,7 @@ private:
63 Core::Memory::Memory& cpu_memory; 67 Core::Memory::Memory& cpu_memory;
64 Tegra::GPU& gpu; 68 Tegra::GPU& gpu;
65 69
66 Common::DynamicLibrary library; 70 std::shared_ptr<Common::DynamicLibrary> library;
67 vk::InstanceDispatch dld; 71 vk::InstanceDispatch dld;
68 72
69 vk::Instance instance; 73 vk::Instance instance;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 1e0fdd3d9..acb143fc7 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -74,7 +74,7 @@ struct ScreenRectVertex {
74 } 74 }
75}; 75};
76 76
77constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) { 77std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
78 // clang-format off 78 // clang-format off
79 return { 2.f / width, 0.f, 0.f, 0.f, 79 return { 2.f / width, 0.f, 0.f, 0.f,
80 0.f, 2.f / height, 0.f, 0.f, 80 0.f, 2.f / height, 0.f, 0.f,
@@ -441,7 +441,12 @@ void BlitScreen::DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& f
441 if (const std::size_t swapchain_images = swapchain.GetImageCount(); 441 if (const std::size_t swapchain_images = swapchain.GetImageCount();
442 swapchain_images != image_count || current_srgb != is_srgb) { 442 swapchain_images != image_count || current_srgb != is_srgb) {
443 current_srgb = is_srgb; 443 current_srgb = is_srgb;
444#ifdef ANDROID
445 // Android is already ordered the same as Switch.
446 image_view_format = current_srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
447#else
444 image_view_format = current_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM; 448 image_view_format = current_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
449#endif
445 image_count = swapchain_images; 450 image_count = swapchain_images;
446 Recreate(); 451 Recreate();
447 } 452 }
@@ -1107,7 +1112,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1107 .pNext = nullptr, 1112 .pNext = nullptr,
1108 .flags = 0, 1113 .flags = 0,
1109 .imageType = VK_IMAGE_TYPE_2D, 1114 .imageType = VK_IMAGE_TYPE_2D,
1110 .format = GetFormat(framebuffer), 1115 .format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
1111 .extent = 1116 .extent =
1112 { 1117 {
1113 .width = (up_scale * framebuffer.width) >> down_shift, 1118 .width = (up_scale * framebuffer.width) >> down_shift,
@@ -1128,14 +1133,14 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1128 const auto create_commit = [&](vk::Image& image) { 1133 const auto create_commit = [&](vk::Image& image) {
1129 return memory_allocator.Commit(image, MemoryUsage::DeviceLocal); 1134 return memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
1130 }; 1135 };
1131 const auto create_image_view = [&](vk::Image& image) { 1136 const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) {
1132 return device.GetLogical().CreateImageView(VkImageViewCreateInfo{ 1137 return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
1133 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1138 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
1134 .pNext = nullptr, 1139 .pNext = nullptr,
1135 .flags = 0, 1140 .flags = 0,
1136 .image = *image, 1141 .image = *image,
1137 .viewType = VK_IMAGE_VIEW_TYPE_2D, 1142 .viewType = VK_IMAGE_VIEW_TYPE_2D,
1138 .format = GetFormat(framebuffer), 1143 .format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
1139 .components = 1144 .components =
1140 { 1145 {
1141 .r = VK_COMPONENT_SWIZZLE_IDENTITY, 1146 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -1165,7 +1170,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1165 const u32 down_shift = Settings::values.resolution_info.down_shift; 1170 const u32 down_shift = Settings::values.resolution_info.down_shift;
1166 aa_image = create_image(true, up_scale, down_shift); 1171 aa_image = create_image(true, up_scale, down_shift);
1167 aa_commit = create_commit(aa_image); 1172 aa_commit = create_commit(aa_image);
1168 aa_image_view = create_image_view(aa_image); 1173 aa_image_view = create_image_view(aa_image, true);
1169 VkExtent2D size{ 1174 VkExtent2D size{
1170 .width = (up_scale * framebuffer.width) >> down_shift, 1175 .width = (up_scale * framebuffer.width) >> down_shift,
1171 .height = (up_scale * framebuffer.height) >> down_shift, 1176 .height = (up_scale * framebuffer.height) >> down_shift,
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 9627eb129..d72d99899 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -7,7 +7,6 @@
7#include <span> 7#include <span>
8#include <vector> 8#include <vector>
9 9
10#include "video_core/buffer_cache/buffer_cache.h"
11#include "video_core/renderer_vulkan/maxwell_to_vk.h" 10#include "video_core/renderer_vulkan/maxwell_to_vk.h"
12#include "video_core/renderer_vulkan/vk_buffer_cache.h" 11#include "video_core/renderer_vulkan/vk_buffer_cache.h"
13#include "video_core/renderer_vulkan/vk_scheduler.h" 12#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -303,9 +302,13 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
303 DescriptorPool& descriptor_pool) 302 DescriptorPool& descriptor_pool)
304 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, 303 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
305 staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_}, 304 staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
306 uint8_pass(device, scheduler, descriptor_pool, staging_pool, compute_pass_descriptor_queue),
307 quad_index_pass(device, scheduler, descriptor_pool, staging_pool, 305 quad_index_pass(device, scheduler, descriptor_pool, staging_pool,
308 compute_pass_descriptor_queue) { 306 compute_pass_descriptor_queue) {
307 if (device.GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
308 // TODO: FixMe: Uint8Pass compute shader does not build on some Qualcomm drivers.
309 uint8_pass = std::make_unique<Uint8Pass>(device, scheduler, descriptor_pool, staging_pool,
310 compute_pass_descriptor_queue);
311 }
309 quad_array_index_buffer = std::make_shared<QuadArrayIndexBuffer>(device_, memory_allocator_, 312 quad_array_index_buffer = std::make_shared<QuadArrayIndexBuffer>(device_, memory_allocator_,
310 scheduler_, staging_pool_); 313 scheduler_, staging_pool_);
311 quad_strip_index_buffer = std::make_shared<QuadStripIndexBuffer>(device_, memory_allocator_, 314 quad_strip_index_buffer = std::make_shared<QuadStripIndexBuffer>(device_, memory_allocator_,
@@ -442,7 +445,9 @@ void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat
442 topology == PrimitiveTopology::QuadStrip); 445 topology == PrimitiveTopology::QuadStrip);
443 } else if (vk_index_type == VK_INDEX_TYPE_UINT8_EXT && !device.IsExtIndexTypeUint8Supported()) { 446 } else if (vk_index_type == VK_INDEX_TYPE_UINT8_EXT && !device.IsExtIndexTypeUint8Supported()) {
444 vk_index_type = VK_INDEX_TYPE_UINT16; 447 vk_index_type = VK_INDEX_TYPE_UINT16;
445 std::tie(vk_buffer, vk_offset) = uint8_pass.Assemble(num_indices, buffer, offset); 448 if (uint8_pass) {
449 std::tie(vk_buffer, vk_offset) = uint8_pass->Assemble(num_indices, buffer, offset);
450 }
446 } 451 }
447 if (vk_buffer == VK_NULL_HANDLE) { 452 if (vk_buffer == VK_NULL_HANDLE) {
448 // Vulkan doesn't support null index buffers. Replace it with our own null buffer. 453 // Vulkan doesn't support null index buffers. Replace it with our own null buffer.
@@ -496,6 +501,40 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
496 } 501 }
497} 502}
498 503
504void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) {
505 boost::container::small_vector<VkBuffer, 32> buffer_handles;
506 for (u32 index = 0; index < bindings.buffers.size(); index++) {
507 auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]);
508 auto handle = buffer.Handle();
509 if (handle == VK_NULL_HANDLE) {
510 bindings.offsets[index] = 0;
511 bindings.sizes[index] = VK_WHOLE_SIZE;
512 if (!device.HasNullDescriptor()) {
513 ReserveNullBuffer();
514 handle = *null_buffer;
515 }
516 }
517 buffer_handles.push_back(handle);
518 }
519 if (device.IsExtExtendedDynamicStateSupported()) {
520 scheduler.Record([bindings = bindings,
521 buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
522 cmdbuf.BindVertexBuffers2EXT(
523 bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
524 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()),
525 reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()),
526 reinterpret_cast<const VkDeviceSize*>(bindings.strides.data()));
527 });
528 } else {
529 scheduler.Record([bindings = bindings,
530 buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
531 cmdbuf.BindVertexBuffers(
532 bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
533 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()));
534 });
535 }
536}
537
499void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, 538void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset,
500 u32 size) { 539 u32 size) {
501 if (!device.IsExtTransformFeedbackSupported()) { 540 if (!device.IsExtTransformFeedbackSupported()) {
@@ -517,6 +556,25 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer,
517 }); 556 });
518} 557}
519 558
559void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {
560 if (!device.IsExtTransformFeedbackSupported()) {
561 // Already logged in the rasterizer
562 return;
563 }
564 boost::container::small_vector<VkBuffer, 4> buffer_handles;
565 for (u32 index = 0; index < bindings.buffers.size(); index++) {
566 auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]);
567 buffer_handles.push_back(buffer.Handle());
568 }
569 scheduler.Record(
570 [bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
571 cmdbuf.BindTransformFeedbackBuffersEXT(
572 0, static_cast<u32>(buffer_handles.size()), buffer_handles.data(),
573 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()),
574 reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()));
575 });
576}
577
520void BufferCacheRuntime::ReserveNullBuffer() { 578void BufferCacheRuntime::ReserveNullBuffer() {
521 if (null_buffer) { 579 if (null_buffer) {
522 return; 580 return;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 5e9602905..92d3e9f32 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -18,6 +18,7 @@ namespace Vulkan {
18class Device; 18class Device;
19class DescriptorPool; 19class DescriptorPool;
20class Scheduler; 20class Scheduler;
21struct HostVertexBinding;
21 22
22class BufferCacheRuntime; 23class BufferCacheRuntime;
23 24
@@ -96,8 +97,10 @@ public:
96 void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); 97 void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
97 98
98 void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); 99 void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride);
100 void BindVertexBuffers(VideoCommon::HostBindings& bindings);
99 101
100 void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); 102 void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
103 void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings);
101 104
102 std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, 105 std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
103 [[maybe_unused]] u32 binding_index, u32 size) { 106 [[maybe_unused]] u32 binding_index, u32 size) {
@@ -139,7 +142,7 @@ private:
139 vk::Buffer null_buffer; 142 vk::Buffer null_buffer;
140 MemoryCommit null_buffer_commit; 143 MemoryCommit null_buffer_commit;
141 144
142 Uint8Pass uint8_pass; 145 std::unique_ptr<Uint8Pass> uint8_pass;
143 QuadIndexedPass quad_index_pass; 146 QuadIndexedPass quad_index_pass;
144}; 147};
145 148
@@ -157,6 +160,7 @@ struct BufferCacheParams {
157 static constexpr bool USE_MEMORY_MAPS = true; 160 static constexpr bool USE_MEMORY_MAPS = true;
158 static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false; 161 static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false;
159 static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true; 162 static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
163 static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true;
160}; 164};
161 165
162using BufferCache = VideoCommon::BufferCache<BufferCacheParams>; 166using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 66dfe5733..5734f51e5 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -114,14 +114,16 @@ Shader::AttributeType CastAttributeType(const FixedPipelineState::VertexAttribut
114 return Shader::AttributeType::Disabled; 114 return Shader::AttributeType::Disabled;
115 case Maxwell::VertexAttribute::Type::SNorm: 115 case Maxwell::VertexAttribute::Type::SNorm:
116 case Maxwell::VertexAttribute::Type::UNorm: 116 case Maxwell::VertexAttribute::Type::UNorm:
117 case Maxwell::VertexAttribute::Type::UScaled:
118 case Maxwell::VertexAttribute::Type::SScaled:
119 case Maxwell::VertexAttribute::Type::Float: 117 case Maxwell::VertexAttribute::Type::Float:
120 return Shader::AttributeType::Float; 118 return Shader::AttributeType::Float;
121 case Maxwell::VertexAttribute::Type::SInt: 119 case Maxwell::VertexAttribute::Type::SInt:
122 return Shader::AttributeType::SignedInt; 120 return Shader::AttributeType::SignedInt;
123 case Maxwell::VertexAttribute::Type::UInt: 121 case Maxwell::VertexAttribute::Type::UInt:
124 return Shader::AttributeType::UnsignedInt; 122 return Shader::AttributeType::UnsignedInt;
123 case Maxwell::VertexAttribute::Type::UScaled:
124 return Shader::AttributeType::UnsignedScaled;
125 case Maxwell::VertexAttribute::Type::SScaled:
126 return Shader::AttributeType::SignedScaled;
125 } 127 }
126 return Shader::AttributeType::Float; 128 return Shader::AttributeType::Float;
127} 129}
@@ -286,14 +288,17 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
286 texture_cache{texture_cache_}, shader_notify{shader_notify_}, 288 texture_cache{texture_cache_}, shader_notify{shader_notify_},
287 use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, 289 use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
288 use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, 290 use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()},
289 workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"), 291 workers(device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY
292 ? 1
293 : (std::max(std::thread::hardware_concurrency(), 2U) - 1),
294 "VkPipelineBuilder"),
290 serialization_thread(1, "VkPipelineSerialization") { 295 serialization_thread(1, "VkPipelineSerialization") {
291 const auto& float_control{device.FloatControlProperties()}; 296 const auto& float_control{device.FloatControlProperties()};
292 const VkDriverId driver_id{device.GetDriverID()}; 297 const VkDriverId driver_id{device.GetDriverID()};
293 profile = Shader::Profile{ 298 profile = Shader::Profile{
294 .supported_spirv = device.SupportedSpirvVersion(), 299 .supported_spirv = device.SupportedSpirvVersion(),
295 .unified_descriptor_binding = true, 300 .unified_descriptor_binding = true,
296 .support_descriptor_aliasing = true, 301 .support_descriptor_aliasing = device.IsDescriptorAliasingSupported(),
297 .support_int8 = device.IsInt8Supported(), 302 .support_int8 = device.IsInt8Supported(),
298 .support_int16 = device.IsShaderInt16Supported(), 303 .support_int16 = device.IsShaderInt16Supported(),
299 .support_int64 = device.IsShaderInt64Supported(), 304 .support_int64 = device.IsShaderInt64Supported(),
@@ -324,6 +329,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
324 .support_derivative_control = true, 329 .support_derivative_control = true,
325 .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), 330 .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
326 .support_native_ndc = device.IsExtDepthClipControlSupported(), 331 .support_native_ndc = device.IsExtDepthClipControlSupported(),
332 .support_scaled_attributes = !device.MustEmulateScaledFormats(),
327 333
328 .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(), 334 .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(),
329 335
@@ -341,8 +347,10 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
341 .has_broken_signed_operations = false, 347 .has_broken_signed_operations = false,
342 .has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY, 348 .has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY,
343 .ignore_nan_fp_comparisons = false, 349 .ignore_nan_fp_comparisons = false,
344 }; 350 .has_broken_spirv_subgroup_mask_vector_extract_dynamic =
351 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY};
345 host_info = Shader::HostTranslateInfo{ 352 host_info = Shader::HostTranslateInfo{
353 .support_float64 = device.IsFloat64Supported(),
346 .support_float16 = device.IsFloat16Supported(), 354 .support_float16 = device.IsFloat16Supported(),
347 .support_int64 = device.IsShaderInt64Supported(), 355 .support_int64 = device.IsShaderInt64Supported(),
348 .needs_demote_reorder = 356 .needs_demote_reorder =
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index c49583013..10ace0420 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -4,10 +4,12 @@
4#include "common/microprofile.h" 4#include "common/microprofile.h"
5#include "common/settings.h" 5#include "common/settings.h"
6#include "common/thread.h" 6#include "common/thread.h"
7#include "core/frontend/emu_window.h"
7#include "video_core/renderer_vulkan/vk_present_manager.h" 8#include "video_core/renderer_vulkan/vk_present_manager.h"
8#include "video_core/renderer_vulkan/vk_scheduler.h" 9#include "video_core/renderer_vulkan/vk_scheduler.h"
9#include "video_core/renderer_vulkan/vk_swapchain.h" 10#include "video_core/renderer_vulkan/vk_swapchain.h"
10#include "video_core/vulkan_common/vulkan_device.h" 11#include "video_core/vulkan_common/vulkan_device.h"
12#include "video_core/vulkan_common/vulkan_surface.h"
11 13
12namespace Vulkan { 14namespace Vulkan {
13 15
@@ -92,14 +94,17 @@ bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat form
92 94
93} // Anonymous namespace 95} // Anonymous namespace
94 96
95PresentManager::PresentManager(Core::Frontend::EmuWindow& render_window_, const Device& device_, 97PresentManager::PresentManager(const vk::Instance& instance_,
98 Core::Frontend::EmuWindow& render_window_, const Device& device_,
96 MemoryAllocator& memory_allocator_, Scheduler& scheduler_, 99 MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
97 Swapchain& swapchain_) 100 Swapchain& swapchain_, vk::SurfaceKHR& surface_)
98 : render_window{render_window_}, device{device_}, 101 : instance{instance_}, render_window{render_window_}, device{device_},
99 memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_}, 102 memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_},
100 blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}, 103 surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
104 swapchain.GetImageViewFormat())},
101 use_present_thread{Settings::values.async_presentation.GetValue()}, 105 use_present_thread{Settings::values.async_presentation.GetValue()},
102 image_count{swapchain.GetImageCount()} { 106 image_count{swapchain.GetImageCount()}, last_render_surface{
107 render_window_.GetWindowInfo().render_surface} {
103 108
104 auto& dld = device.GetLogical(); 109 auto& dld = device.GetLogical();
105 cmdpool = dld.CreateCommandPool({ 110 cmdpool = dld.CreateCommandPool({
@@ -286,14 +291,45 @@ void PresentManager::PresentThread(std::stop_token token) {
286 } 291 }
287} 292}
288 293
294void PresentManager::NotifySurfaceChanged() {
295#ifdef ANDROID
296 std::scoped_lock lock{recreate_surface_mutex};
297 recreate_surface_cv.notify_one();
298#endif
299}
300
289void PresentManager::CopyToSwapchain(Frame* frame) { 301void PresentManager::CopyToSwapchain(Frame* frame) {
290 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain); 302 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
291 303
292 const auto recreate_swapchain = [&] { 304 const auto recreate_swapchain = [&] {
293 swapchain.Create(frame->width, frame->height, frame->is_srgb); 305 swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
294 image_count = swapchain.GetImageCount(); 306 image_count = swapchain.GetImageCount();
295 }; 307 };
296 308
309#ifdef ANDROID
310 std::unique_lock lock{recreate_surface_mutex};
311
312 const auto needs_recreation = [&] {
313 if (last_render_surface != render_window.GetWindowInfo().render_surface) {
314 return true;
315 }
316 if (swapchain.NeedsRecreation(frame->is_srgb)) {
317 return true;
318 }
319 return false;
320 };
321
322 recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
323 [&]() { return !needs_recreation(); });
324
325 // If the frontend recreated the surface, recreate the renderer surface and swapchain.
326 if (last_render_surface != render_window.GetWindowInfo().render_surface) {
327 last_render_surface = render_window.GetWindowInfo().render_surface;
328 surface = CreateSurface(instance, render_window.GetWindowInfo());
329 recreate_swapchain();
330 }
331#endif
332
297 // If the size or colorspace of the incoming frames has changed, recreate the swapchain 333 // If the size or colorspace of the incoming frames has changed, recreate the swapchain
298 // to account for that. 334 // to account for that.
299 const bool srgb_changed = swapchain.NeedsRecreation(frame->is_srgb); 335 const bool srgb_changed = swapchain.NeedsRecreation(frame->is_srgb);
@@ -436,7 +472,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
436 472
437 // Submit the image copy/blit to the swapchain 473 // Submit the image copy/blit to the swapchain
438 { 474 {
439 std::scoped_lock lock{scheduler.submit_mutex}; 475 std::scoped_lock submit_lock{scheduler.submit_mutex};
440 switch (const VkResult result = 476 switch (const VkResult result =
441 device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) { 477 device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) {
442 case VK_SUCCESS: 478 case VK_SUCCESS:
@@ -454,4 +490,4 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
454 swapchain.Present(render_semaphore); 490 swapchain.Present(render_semaphore);
455} 491}
456 492
457} // namespace Vulkan 493} // namespace Vulkan \ No newline at end of file
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index 420a775e2..4ac2e2395 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -37,8 +37,9 @@ struct Frame {
37 37
38class PresentManager { 38class PresentManager {
39public: 39public:
40 PresentManager(Core::Frontend::EmuWindow& render_window, const Device& device, 40 PresentManager(const vk::Instance& instance, Core::Frontend::EmuWindow& render_window,
41 MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain); 41 const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler,
42 Swapchain& swapchain, vk::SurfaceKHR& surface);
42 ~PresentManager(); 43 ~PresentManager();
43 44
44 /// Returns the last used presentation frame 45 /// Returns the last used presentation frame
@@ -54,30 +55,38 @@ public:
54 /// Waits for the present thread to finish presenting all queued frames. 55 /// Waits for the present thread to finish presenting all queued frames.
55 void WaitPresent(); 56 void WaitPresent();
56 57
58 /// This is called to notify the rendering backend of a surface change
59 void NotifySurfaceChanged();
60
57private: 61private:
58 void PresentThread(std::stop_token token); 62 void PresentThread(std::stop_token token);
59 63
60 void CopyToSwapchain(Frame* frame); 64 void CopyToSwapchain(Frame* frame);
61 65
62private: 66private:
67 const vk::Instance& instance;
63 Core::Frontend::EmuWindow& render_window; 68 Core::Frontend::EmuWindow& render_window;
64 const Device& device; 69 const Device& device;
65 MemoryAllocator& memory_allocator; 70 MemoryAllocator& memory_allocator;
66 Scheduler& scheduler; 71 Scheduler& scheduler;
67 Swapchain& swapchain; 72 Swapchain& swapchain;
73 vk::SurfaceKHR& surface;
68 vk::CommandPool cmdpool; 74 vk::CommandPool cmdpool;
69 std::vector<Frame> frames; 75 std::vector<Frame> frames;
70 std::queue<Frame*> present_queue; 76 std::queue<Frame*> present_queue;
71 std::queue<Frame*> free_queue; 77 std::queue<Frame*> free_queue;
72 std::condition_variable_any frame_cv; 78 std::condition_variable_any frame_cv;
73 std::condition_variable free_cv; 79 std::condition_variable free_cv;
80 std::condition_variable recreate_surface_cv;
74 std::mutex swapchain_mutex; 81 std::mutex swapchain_mutex;
82 std::mutex recreate_surface_mutex;
75 std::mutex queue_mutex; 83 std::mutex queue_mutex;
76 std::mutex free_mutex; 84 std::mutex free_mutex;
77 std::jthread present_thread; 85 std::jthread present_thread;
78 bool blit_supported; 86 bool blit_supported;
79 bool use_present_thread; 87 bool use_present_thread;
80 std::size_t image_count; 88 std::size_t image_count{};
89 void* last_render_surface{};
81}; 90};
82 91
83} // namespace Vulkan 92} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 8d3a9736b..84e3a30cc 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -188,7 +188,14 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
188 FlushWork(); 188 FlushWork();
189 gpu_memory->FlushCaching(); 189 gpu_memory->FlushCaching();
190 190
191#if ANDROID
192 if (Settings::IsGPULevelHigh()) {
193 // This is problematic on Android, disable on GPU Normal.
194 query_cache.UpdateCounters();
195 }
196#else
191 query_cache.UpdateCounters(); 197 query_cache.UpdateCounters();
198#endif
192 199
193 GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()}; 200 GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()};
194 if (!pipeline) { 201 if (!pipeline) {
@@ -272,7 +279,14 @@ void RasterizerVulkan::DrawTexture() {
272 SCOPE_EXIT({ gpu.TickWork(); }); 279 SCOPE_EXIT({ gpu.TickWork(); });
273 FlushWork(); 280 FlushWork();
274 281
282#if ANDROID
283 if (Settings::IsGPULevelHigh()) {
284 // This is problematic on Android, disable on GPU Normal.
285 query_cache.UpdateCounters();
286 }
287#else
275 query_cache.UpdateCounters(); 288 query_cache.UpdateCounters();
289#endif
276 290
277 texture_cache.SynchronizeGraphicsDescriptors(); 291 texture_cache.SynchronizeGraphicsDescriptors();
278 texture_cache.UpdateRenderTargets(false); 292 texture_cache.UpdateRenderTargets(false);
@@ -743,7 +757,11 @@ void RasterizerVulkan::LoadDiskResources(u64 title_id, std::stop_token stop_load
743} 757}
744 758
745void RasterizerVulkan::FlushWork() { 759void RasterizerVulkan::FlushWork() {
760#ifdef ANDROID
761 static constexpr u32 DRAWS_TO_DISPATCH = 1024;
762#else
746 static constexpr u32 DRAWS_TO_DISPATCH = 4096; 763 static constexpr u32 DRAWS_TO_DISPATCH = 4096;
764#endif // ANDROID
747 765
748 // Only check multiples of 8 draws 766 // Only check multiples of 8 draws
749 static_assert(DRAWS_TO_DISPATCH % 8 == 0); 767 static_assert(DRAWS_TO_DISPATCH % 8 == 0);
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 80455ec08..17ef61147 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -239,7 +239,14 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
239void Scheduler::AllocateNewContext() { 239void Scheduler::AllocateNewContext() {
240 // Enable counters once again. These are disabled when a command buffer is finished. 240 // Enable counters once again. These are disabled when a command buffer is finished.
241 if (query_cache) { 241 if (query_cache) {
242#if ANDROID
243 if (Settings::IsGPULevelHigh()) {
244 // This is problematic on Android, disable on GPU Normal.
245 query_cache->UpdateCounters();
246 }
247#else
242 query_cache->UpdateCounters(); 248 query_cache->UpdateCounters();
249#endif
243 } 250 }
244} 251}
245 252
@@ -250,7 +257,14 @@ void Scheduler::InvalidateState() {
250} 257}
251 258
252void Scheduler::EndPendingOperations() { 259void Scheduler::EndPendingOperations() {
260#if ANDROID
261 if (Settings::IsGPULevelHigh()) {
262 // This is problematic on Android, disable on GPU Normal.
263 query_cache->DisableStreams();
264 }
265#else
253 query_cache->DisableStreams(); 266 query_cache->DisableStreams();
267#endif
254 EndRenderPass(); 268 EndRenderPass();
255} 269}
256 270
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 8c0dec590..d3cddac69 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -107,16 +107,17 @@ VkCompositeAlphaFlagBitsKHR ChooseAlphaFlags(const VkSurfaceCapabilitiesKHR& cap
107Swapchain::Swapchain(VkSurfaceKHR surface_, const Device& device_, Scheduler& scheduler_, 107Swapchain::Swapchain(VkSurfaceKHR surface_, const Device& device_, Scheduler& scheduler_,
108 u32 width_, u32 height_, bool srgb) 108 u32 width_, u32 height_, bool srgb)
109 : surface{surface_}, device{device_}, scheduler{scheduler_} { 109 : surface{surface_}, device{device_}, scheduler{scheduler_} {
110 Create(width_, height_, srgb); 110 Create(surface_, width_, height_, srgb);
111} 111}
112 112
113Swapchain::~Swapchain() = default; 113Swapchain::~Swapchain() = default;
114 114
115void Swapchain::Create(u32 width_, u32 height_, bool srgb) { 115void Swapchain::Create(VkSurfaceKHR surface_, u32 width_, u32 height_, bool srgb) {
116 is_outdated = false; 116 is_outdated = false;
117 is_suboptimal = false; 117 is_suboptimal = false;
118 width = width_; 118 width = width_;
119 height = height_; 119 height = height_;
120 surface = surface_;
120 121
121 const auto physical_device = device.GetPhysical(); 122 const auto physical_device = device.GetPhysical();
122 const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)}; 123 const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)};
@@ -230,7 +231,12 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
230 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, 231 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
231 .queueFamilyIndexCount = 0, 232 .queueFamilyIndexCount = 0,
232 .pQueueFamilyIndices = nullptr, 233 .pQueueFamilyIndices = nullptr,
234#ifdef ANDROID
235 // On Android, do not allow surface rotation to deviate from the frontend.
236 .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
237#else
233 .preTransform = capabilities.currentTransform, 238 .preTransform = capabilities.currentTransform,
239#endif
234 .compositeAlpha = alpha_flags, 240 .compositeAlpha = alpha_flags,
235 .presentMode = present_mode, 241 .presentMode = present_mode,
236 .clipped = VK_FALSE, 242 .clipped = VK_FALSE,
@@ -266,7 +272,12 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
266 272
267 images = swapchain.GetImages(); 273 images = swapchain.GetImages();
268 image_count = static_cast<u32>(images.size()); 274 image_count = static_cast<u32>(images.size());
275#ifdef ANDROID
276 // Android is already ordered the same as Switch.
277 image_view_format = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
278#else
269 image_view_format = srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM; 279 image_view_format = srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
280#endif
270} 281}
271 282
272void Swapchain::CreateSemaphores() { 283void Swapchain::CreateSemaphores() {
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
index bf1ea7254..b8a1465a6 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.h
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -24,7 +24,7 @@ public:
24 ~Swapchain(); 24 ~Swapchain();
25 25
26 /// Creates (or recreates) the swapchain with a given size. 26 /// Creates (or recreates) the swapchain with a given size.
27 void Create(u32 width, u32 height, bool srgb); 27 void Create(VkSurfaceKHR surface, u32 width, u32 height, bool srgb);
28 28
29 /// Acquires the next image in the swapchain, waits as needed. 29 /// Acquires the next image in the swapchain, waits as needed.
30 bool AcquireNextImage(); 30 bool AcquireNextImage();
@@ -118,7 +118,7 @@ private:
118 118
119 bool NeedsPresentModeUpdate() const; 119 bool NeedsPresentModeUpdate() const;
120 120
121 const VkSurfaceKHR surface; 121 VkSurfaceKHR surface;
122 const Device& device; 122 const Device& device;
123 Scheduler& scheduler; 123 Scheduler& scheduler;
124 124
diff --git a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
index db04943eb..a802d3c49 100644
--- a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
+++ b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
@@ -1,6 +1,10 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
5#include <adrenotools/driver.h>
6#endif
7
4#include "common/literals.h" 8#include "common/literals.h"
5#include "video_core/host_shaders/vulkan_turbo_mode_comp_spv.h" 9#include "video_core/host_shaders/vulkan_turbo_mode_comp_spv.h"
6#include "video_core/renderer_vulkan/renderer_vulkan.h" 10#include "video_core/renderer_vulkan/renderer_vulkan.h"
@@ -13,7 +17,10 @@ namespace Vulkan {
13using namespace Common::Literals; 17using namespace Common::Literals;
14 18
15TurboMode::TurboMode(const vk::Instance& instance, const vk::InstanceDispatch& dld) 19TurboMode::TurboMode(const vk::Instance& instance, const vk::InstanceDispatch& dld)
16 : m_device{CreateDevice(instance, dld, VK_NULL_HANDLE)}, m_allocator{m_device, false} { 20#ifndef ANDROID
21 : m_device{CreateDevice(instance, dld, VK_NULL_HANDLE)}, m_allocator{m_device, false}
22#endif
23{
17 { 24 {
18 std::scoped_lock lk{m_submission_lock}; 25 std::scoped_lock lk{m_submission_lock};
19 m_submission_time = std::chrono::steady_clock::now(); 26 m_submission_time = std::chrono::steady_clock::now();
@@ -30,6 +37,7 @@ void TurboMode::QueueSubmitted() {
30} 37}
31 38
32void TurboMode::Run(std::stop_token stop_token) { 39void TurboMode::Run(std::stop_token stop_token) {
40#ifndef ANDROID
33 auto& dld = m_device.GetLogical(); 41 auto& dld = m_device.GetLogical();
34 42
35 // Allocate buffer. 2MiB should be sufficient. 43 // Allocate buffer. 2MiB should be sufficient.
@@ -142,8 +150,14 @@ void TurboMode::Run(std::stop_token stop_token) {
142 // Create a single command buffer. 150 // Create a single command buffer.
143 auto cmdbufs = command_pool.Allocate(1, VK_COMMAND_BUFFER_LEVEL_PRIMARY); 151 auto cmdbufs = command_pool.Allocate(1, VK_COMMAND_BUFFER_LEVEL_PRIMARY);
144 auto cmdbuf = vk::CommandBuffer{cmdbufs[0], m_device.GetDispatchLoader()}; 152 auto cmdbuf = vk::CommandBuffer{cmdbufs[0], m_device.GetDispatchLoader()};
153#endif
145 154
146 while (!stop_token.stop_requested()) { 155 while (!stop_token.stop_requested()) {
156#ifdef ANDROID
157#ifdef ARCHITECTURE_arm64
158 adrenotools_set_turbo(true);
159#endif
160#else
147 // Reset the fence. 161 // Reset the fence.
148 fence.Reset(); 162 fence.Reset();
149 163
@@ -209,7 +223,7 @@ void TurboMode::Run(std::stop_token stop_token) {
209 223
210 // Wait for completion. 224 // Wait for completion.
211 fence.Wait(); 225 fence.Wait();
212 226#endif
213 // Wait for the next graphics queue submission if necessary. 227 // Wait for the next graphics queue submission if necessary.
214 std::unique_lock lk{m_submission_lock}; 228 std::unique_lock lk{m_submission_lock};
215 Common::CondvarWait(m_submission_cv, lk, stop_token, [this] { 229 Common::CondvarWait(m_submission_cv, lk, stop_token, [this] {
@@ -217,6 +231,9 @@ void TurboMode::Run(std::stop_token stop_token) {
217 std::chrono::milliseconds{100}; 231 std::chrono::milliseconds{100};
218 }); 232 });
219 } 233 }
234#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
235 adrenotools_set_turbo(false);
236#endif
220} 237}
221 238
222} // namespace Vulkan 239} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_turbo_mode.h b/src/video_core/renderer_vulkan/vk_turbo_mode.h
index 99b5ac50b..9341c9867 100644
--- a/src/video_core/renderer_vulkan/vk_turbo_mode.h
+++ b/src/video_core/renderer_vulkan/vk_turbo_mode.h
@@ -23,8 +23,10 @@ public:
23private: 23private:
24 void Run(std::stop_token stop_token); 24 void Run(std::stop_token stop_token);
25 25
26#ifndef ANDROID
26 Device m_device; 27 Device m_device;
27 MemoryAllocator m_allocator; 28 MemoryAllocator m_allocator;
29#endif
28 std::mutex m_submission_lock; 30 std::mutex m_submission_lock;
29 std::condition_variable_any m_submission_cv; 31 std::condition_variable_any m_submission_cv;
30 std::chrono::time_point<std::chrono::steady_clock> m_submission_time{}; 32 std::chrono::time_point<std::chrono::steady_clock> m_submission_time{};
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h
index 310fb551a..e77b576ec 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.h
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h
@@ -31,7 +31,7 @@ struct DescriptorUpdateEntry {
31class UpdateDescriptorQueue final { 31class UpdateDescriptorQueue final {
32 // This should be plenty for the vast majority of cases. Most desktop platforms only 32 // This should be plenty for the vast majority of cases. Most desktop platforms only
33 // provide up to 3 swapchain images. 33 // provide up to 3 swapchain images.
34 static constexpr size_t FRAMES_IN_FLIGHT = 5; 34 static constexpr size_t FRAMES_IN_FLIGHT = 7;
35 static constexpr size_t FRAME_PAYLOAD_SIZE = 0x20000; 35 static constexpr size_t FRAME_PAYLOAD_SIZE = 0x20000;
36 static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT; 36 static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT;
37 37
diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp
index e8ddde691..b72788c6d 100644
--- a/src/video_core/texture_cache/image_info.cpp
+++ b/src/video_core/texture_cache/image_info.cpp
@@ -22,6 +22,9 @@ using Tegra::Texture::TICEntry;
22using VideoCore::Surface::PixelFormat; 22using VideoCore::Surface::PixelFormat;
23using VideoCore::Surface::SurfaceType; 23using VideoCore::Surface::SurfaceType;
24 24
25constexpr u32 RescaleHeightThreshold = 288;
26constexpr u32 DownscaleHeightThreshold = 512;
27
25ImageInfo::ImageInfo(const TICEntry& config) noexcept { 28ImageInfo::ImageInfo(const TICEntry& config) noexcept {
26 forced_flushed = config.IsPitchLinear() && !Settings::values.use_reactive_flushing.GetValue(); 29 forced_flushed = config.IsPitchLinear() && !Settings::values.use_reactive_flushing.GetValue();
27 dma_downloaded = forced_flushed; 30 dma_downloaded = forced_flushed;
@@ -113,8 +116,9 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept {
113 layer_stride = CalculateLayerStride(*this); 116 layer_stride = CalculateLayerStride(*this);
114 maybe_unaligned_layer_stride = CalculateLayerSize(*this); 117 maybe_unaligned_layer_stride = CalculateLayerSize(*this);
115 rescaleable &= (block.depth == 0) && resources.levels == 1; 118 rescaleable &= (block.depth == 0) && resources.levels == 1;
116 rescaleable &= size.height > 256 || GetFormatType(format) != SurfaceType::ColorTexture; 119 rescaleable &= size.height > RescaleHeightThreshold ||
117 downscaleable = size.height > 512; 120 GetFormatType(format) != SurfaceType::ColorTexture;
121 downscaleable = size.height > DownscaleHeightThreshold;
118 } 122 }
119} 123}
120 124
@@ -152,8 +156,8 @@ ImageInfo::ImageInfo(const Maxwell3D::Regs::RenderTargetConfig& ct,
152 size.depth = ct.depth; 156 size.depth = ct.depth;
153 } else { 157 } else {
154 rescaleable = block.depth == 0; 158 rescaleable = block.depth == 0;
155 rescaleable &= size.height > 256; 159 rescaleable &= size.height > RescaleHeightThreshold;
156 downscaleable = size.height > 512; 160 downscaleable = size.height > DownscaleHeightThreshold;
157 type = ImageType::e2D; 161 type = ImageType::e2D;
158 resources.layers = ct.depth; 162 resources.layers = ct.depth;
159 } 163 }
@@ -232,8 +236,8 @@ ImageInfo::ImageInfo(const Fermi2D::Surface& config) noexcept {
232 .height = config.height, 236 .height = config.height,
233 .depth = 1, 237 .depth = 1,
234 }; 238 };
235 rescaleable = block.depth == 0 && size.height > 256; 239 rescaleable = block.depth == 0 && size.height > RescaleHeightThreshold;
236 downscaleable = size.height > 512; 240 downscaleable = size.height > DownscaleHeightThreshold;
237 } 241 }
238} 242}
239 243
@@ -275,8 +279,8 @@ ImageInfo::ImageInfo(const Tegra::DMA::ImageOperand& config) noexcept {
275 resources.layers = 1; 279 resources.layers = 1;
276 layer_stride = CalculateLayerStride(*this); 280 layer_stride = CalculateLayerStride(*this);
277 maybe_unaligned_layer_stride = CalculateLayerSize(*this); 281 maybe_unaligned_layer_stride = CalculateLayerSize(*this);
278 rescaleable = block.depth == 0 && size.height > 256; 282 rescaleable = block.depth == 0 && size.height > RescaleHeightThreshold;
279 downscaleable = size.height > 512; 283 downscaleable = size.height > DownscaleHeightThreshold;
280} 284}
281 285
282} // namespace VideoCommon 286} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 2cf082c5d..c7f7448e9 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -850,15 +850,11 @@ void TextureCache<P>::PopAsyncFlushes() {
850template <class P> 850template <class P>
851ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, bool is_upload) { 851ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, bool is_upload) {
852 const ImageInfo dst_info(operand); 852 const ImageInfo dst_info(operand);
853 const ImageId dst_id = FindDMAImage(dst_info, operand.address); 853 const ImageId image_id = FindDMAImage(dst_info, operand.address);
854 if (!dst_id) { 854 if (!image_id) {
855 return NULL_IMAGE_ID;
856 }
857 auto& image = slot_images[dst_id];
858 if (False(image.flags & ImageFlagBits::GpuModified)) {
859 // No need to waste time on an image that's synced with guest
860 return NULL_IMAGE_ID; 855 return NULL_IMAGE_ID;
861 } 856 }
857 auto& image = slot_images[image_id];
862 if (!is_upload && !image.info.dma_downloaded) { 858 if (!is_upload && !image.info.dma_downloaded) {
863 // Force a full sync. 859 // Force a full sync.
864 image.info.dma_downloaded = true; 860 image.info.dma_downloaded = true;
@@ -868,7 +864,7 @@ ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, boo
868 if (!base) { 864 if (!base) {
869 return NULL_IMAGE_ID; 865 return NULL_IMAGE_ID;
870 } 866 }
871 return dst_id; 867 return image_id;
872} 868}
873 869
874template <class P> 870template <class P>
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
index 10a001b8f..9de484c29 100644
--- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp
+++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
@@ -13,11 +13,39 @@ VkBool32 Callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
13 [[maybe_unused]] void* user_data) { 13 [[maybe_unused]] void* user_data) {
14 // Skip logging known false-positive validation errors 14 // Skip logging known false-positive validation errors
15 switch (static_cast<u32>(data->messageIdNumber)) { 15 switch (static_cast<u32>(data->messageIdNumber)) {
16#ifdef ANDROID
17 case 0xbf9cf353u: // VUID-vkCmdBindVertexBuffers2-pBuffers-04111
18 // The below are due to incorrect reporting of extendedDynamicState
19 case 0x1093bebbu: // VUID-vkCmdSetCullMode-None-03384
20 case 0x9215850fu: // VUID-vkCmdSetDepthTestEnable-None-03352
21 case 0x86bf18dcu: // VUID-vkCmdSetDepthWriteEnable-None-03354
22 case 0x0792ad08u: // VUID-vkCmdSetStencilOp-None-03351
23 case 0x93e1ba4eu: // VUID-vkCmdSetFrontFace-None-03383
24 case 0xac9c13c5u: // VUID-vkCmdSetStencilTestEnable-None-03350
25 case 0xc9a2001bu: // VUID-vkCmdSetDepthBoundsTestEnable-None-03349
26 case 0x8b7159a7u: // VUID-vkCmdSetDepthCompareOp-None-03353
27 // The below are due to incorrect reporting of extendedDynamicState2
28 case 0xb13c8036u: // VUID-vkCmdSetDepthBiasEnable-None-04872
29 case 0xdff2e5c1u: // VUID-vkCmdSetRasterizerDiscardEnable-None-04871
30 case 0x0cc85f41u: // VUID-vkCmdSetPrimitiveRestartEnable-None-04866
31 case 0x01257b492: // VUID-vkCmdSetLogicOpEXT-None-0486
32 // The below are due to incorrect reporting of vertexInputDynamicState
33 case 0x398e0dabu: // VUID-vkCmdSetVertexInputEXT-None-04790
34 // The below are due to incorrect reporting of extendedDynamicState3
35 case 0x970c11a5u: // VUID-vkCmdSetColorWriteMaskEXT-extendedDynamicState3ColorWriteMask-07364
36 case 0x6b453f78u: // VUID-vkCmdSetColorBlendEnableEXT-extendedDynamicState3ColorBlendEnable-07355
37 case 0xf66469d0u: // VUID-vkCmdSetColorBlendEquationEXT-extendedDynamicState3ColorBlendEquation-07356
38 case 0x1d43405eu: // VUID-vkCmdSetLogicOpEnableEXT-extendedDynamicState3LogicOpEnable-07365
39 case 0x638462e8u: // VUID-vkCmdSetDepthClampEnableEXT-extendedDynamicState3DepthClampEnable-07448
40 // Misc
41 case 0xe0a2da61u: // VUID-vkCmdDrawIndexed-format-07753
42#else
16 case 0x682a878au: // VUID-vkCmdBindVertexBuffers2EXT-pBuffers-parameter 43 case 0x682a878au: // VUID-vkCmdBindVertexBuffers2EXT-pBuffers-parameter
17 case 0x99fb7dfdu: // UNASSIGNED-RequiredParameter (vkCmdBindVertexBuffers2EXT pBuffers[0]) 44 case 0x99fb7dfdu: // UNASSIGNED-RequiredParameter (vkCmdBindVertexBuffers2EXT pBuffers[0])
18 case 0xe8616bf2u: // Bound VkDescriptorSet 0x0[] was destroyed. Likely push_descriptor related 45 case 0xe8616bf2u: // Bound VkDescriptorSet 0x0[] was destroyed. Likely push_descriptor related
19 case 0x1608dec0u: // Image layout in vkUpdateDescriptorSet doesn't match descriptor use 46 case 0x1608dec0u: // Image layout in vkUpdateDescriptorSet doesn't match descriptor use
20 case 0x55362756u: // Descriptor binding and framebuffer attachment overlap 47 case 0x55362756u: // Descriptor binding and framebuffer attachment overlap
48#endif
21 return VK_FALSE; 49 return VK_FALSE;
22 default: 50 default:
23 break; 51 break;
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index aea677cb3..a46f9beed 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -18,6 +18,10 @@
18#include "video_core/vulkan_common/vulkan_device.h" 18#include "video_core/vulkan_common/vulkan_device.h"
19#include "video_core/vulkan_common/vulkan_wrapper.h" 19#include "video_core/vulkan_common/vulkan_wrapper.h"
20 20
21#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
22#include <adrenotools/bcenabler.h>
23#endif
24
21namespace Vulkan { 25namespace Vulkan {
22using namespace Common::Literals; 26using namespace Common::Literals;
23namespace { 27namespace {
@@ -262,6 +266,32 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
262 return format_properties; 266 return format_properties;
263} 267}
264 268
269#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
270void OverrideBcnFormats(std::unordered_map<VkFormat, VkFormatProperties>& format_properties) {
271 // These properties are extracted from Adreno driver 512.687.0
272 constexpr VkFormatFeatureFlags tiling_features{
273 VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT |
274 VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
275 VK_FORMAT_FEATURE_TRANSFER_DST_BIT};
276
277 constexpr VkFormatFeatureFlags buffer_features{VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT};
278
279 static constexpr std::array bcn_formats{
280 VK_FORMAT_BC1_RGBA_SRGB_BLOCK, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_BC2_SRGB_BLOCK,
281 VK_FORMAT_BC2_UNORM_BLOCK, VK_FORMAT_BC3_SRGB_BLOCK, VK_FORMAT_BC3_UNORM_BLOCK,
282 VK_FORMAT_BC4_SNORM_BLOCK, VK_FORMAT_BC4_UNORM_BLOCK, VK_FORMAT_BC5_SNORM_BLOCK,
283 VK_FORMAT_BC5_UNORM_BLOCK, VK_FORMAT_BC6H_SFLOAT_BLOCK, VK_FORMAT_BC6H_UFLOAT_BLOCK,
284 VK_FORMAT_BC7_SRGB_BLOCK, VK_FORMAT_BC7_UNORM_BLOCK,
285 };
286
287 for (const auto format : bcn_formats) {
288 format_properties[format].linearTilingFeatures = tiling_features;
289 format_properties[format].optimalTilingFeatures = tiling_features;
290 format_properties[format].bufferFeatures = buffer_features;
291 }
292}
293#endif
294
265NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical, 295NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
266 const std::set<std::string, std::less<>>& exts) { 296 const std::set<std::string, std::less<>>& exts) {
267 if (exts.contains(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) { 297 if (exts.contains(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) {
@@ -302,6 +332,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
302 const bool is_suitable = GetSuitability(surface != nullptr); 332 const bool is_suitable = GetSuitability(surface != nullptr);
303 333
304 const VkDriverId driver_id = properties.driver.driverID; 334 const VkDriverId driver_id = properties.driver.driverID;
335 const auto device_id = properties.properties.deviceID;
305 const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV; 336 const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV;
306 const bool is_amd_driver = 337 const bool is_amd_driver =
307 driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE; 338 driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE;
@@ -310,9 +341,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
310 const bool is_intel_anv = driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA; 341 const bool is_intel_anv = driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA;
311 const bool is_nvidia = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY; 342 const bool is_nvidia = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY;
312 const bool is_mvk = driver_id == VK_DRIVER_ID_MOLTENVK; 343 const bool is_mvk = driver_id == VK_DRIVER_ID_MOLTENVK;
344 const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
345 const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP;
346 const bool is_s8gen2 = device_id == 0x43050a01;
313 347
314 if (is_mvk && !is_suitable) { 348 if ((is_mvk || is_qualcomm || is_turnip) && !is_suitable) {
315 LOG_WARNING(Render_Vulkan, "Unsuitable driver is MoltenVK, continuing anyway"); 349 LOG_WARNING(Render_Vulkan, "Unsuitable driver, continuing anyway");
316 } else if (!is_suitable) { 350 } else if (!is_suitable) {
317 throw vk::Exception(VK_ERROR_INCOMPATIBLE_DRIVER); 351 throw vk::Exception(VK_ERROR_INCOMPATIBLE_DRIVER);
318 } 352 }
@@ -352,9 +386,64 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
352 IsFormatSupported(VK_FORMAT_D24_UNORM_S8_UINT, 386 IsFormatSupported(VK_FORMAT_D24_UNORM_S8_UINT,
353 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, FormatType::Optimal); 387 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, FormatType::Optimal);
354 388
389 supports_conditional_barriers = !(is_intel_anv || is_intel_windows);
390
355 CollectPhysicalMemoryInfo(); 391 CollectPhysicalMemoryInfo();
356 CollectToolingInfo(); 392 CollectToolingInfo();
357 393
394#ifdef ANDROID
395 if (is_qualcomm || is_turnip) {
396 LOG_WARNING(Render_Vulkan,
397 "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color");
398 extensions.custom_border_color = false;
399 loaded_extensions.erase(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
400 }
401
402 if (is_qualcomm) {
403 must_emulate_scaled_formats = true;
404
405 LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state");
406 extensions.extended_dynamic_state = false;
407 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
408
409 LOG_WARNING(Render_Vulkan,
410 "Qualcomm drivers have a slow VK_KHR_push_descriptor implementation");
411 extensions.push_descriptor = false;
412 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
413
414#ifdef ARCHITECTURE_arm64
415 // Patch the driver to enable BCn textures.
416 const auto major = (properties.properties.driverVersion >> 24) << 2;
417 const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU;
418 const auto vendor = properties.properties.vendorID;
419 const auto patch_status = adrenotools_get_bcn_type(major, minor, vendor);
420
421 if (patch_status == ADRENOTOOLS_BCN_PATCH) {
422 LOG_INFO(Render_Vulkan, "Patching Adreno driver to support BCn texture formats");
423 if (adrenotools_patch_bcn(
424 reinterpret_cast<void*>(dld.vkGetPhysicalDeviceFormatProperties))) {
425 OverrideBcnFormats(format_properties);
426 } else {
427 LOG_ERROR(Render_Vulkan, "Patch failed! Driver code may now crash");
428 }
429 } else if (patch_status == ADRENOTOOLS_BCN_BLOB) {
430 LOG_INFO(Render_Vulkan, "Adreno driver supports BCn textures without patches");
431 } else {
432 LOG_WARNING(Render_Vulkan, "Adreno driver can't be patched to enable BCn textures");
433 }
434#endif // ARCHITECTURE_arm64
435 }
436
437 const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
438 if (is_arm) {
439 must_emulate_scaled_formats = true;
440
441 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
442 extensions.extended_dynamic_state = false;
443 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
444 }
445#endif // ANDROID
446
358 if (is_nvidia) { 447 if (is_nvidia) {
359 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; 448 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
360 const auto arch = GetNvidiaArchitecture(physical, supported_extensions); 449 const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
@@ -388,7 +477,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
388 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 477 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
389 } 478 }
390 } 479 }
391 if (extensions.extended_dynamic_state2 && is_radv) { 480 if (extensions.extended_dynamic_state2 && (is_radv || is_qualcomm)) {
392 const u32 version = (properties.properties.driverVersion << 3) >> 3; 481 const u32 version = (properties.properties.driverVersion << 3) >> 3;
393 if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) { 482 if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) {
394 LOG_WARNING( 483 LOG_WARNING(
@@ -415,7 +504,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
415 dynamic_state3_enables = false; 504 dynamic_state3_enables = false;
416 } 505 }
417 } 506 }
418 if (extensions.vertex_input_dynamic_state && is_radv) { 507 if (extensions.vertex_input_dynamic_state && (is_radv || is_qualcomm)) {
508 // Qualcomm S8gen2 drivers do not properly support vertex_input_dynamic_state.
419 // TODO(ameerj): Blacklist only offending driver versions 509 // TODO(ameerj): Blacklist only offending driver versions
420 // TODO(ameerj): Confirm if RDNA1 is affected 510 // TODO(ameerj): Confirm if RDNA1 is affected
421 const bool is_rdna2 = 511 const bool is_rdna2 =
@@ -467,8 +557,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
467 LOG_WARNING(Render_Vulkan, "Intel proprietary drivers do not support MSAA image blits"); 557 LOG_WARNING(Render_Vulkan, "Intel proprietary drivers do not support MSAA image blits");
468 cant_blit_msaa = true; 558 cant_blit_msaa = true;
469 } 559 }
470 if (is_intel_anv) { 560 if (is_intel_anv || (is_qualcomm && !is_s8gen2)) {
471 LOG_WARNING(Render_Vulkan, "ANV driver does not support native BGR format"); 561 LOG_WARNING(Render_Vulkan, "Driver does not support native BGR format");
472 must_emulate_bgr565 = true; 562 must_emulate_bgr565 = true;
473 } 563 }
474 if (extensions.push_descriptor && is_intel_anv) { 564 if (extensions.push_descriptor && is_intel_anv) {
@@ -633,7 +723,8 @@ bool Device::ShouldBoostClocks() const {
633 driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE || 723 driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE ||
634 driver_id == VK_DRIVER_ID_MESA_RADV || driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY || 724 driver_id == VK_DRIVER_ID_MESA_RADV || driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY ||
635 driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS || 725 driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS ||
636 driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA; 726 driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA ||
727 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP;
637 728
638 const bool is_steam_deck = vendor_id == 0x1002 && device_id == 0x163F; 729 const bool is_steam_deck = vendor_id == 0x1002 && device_id == 0x163F;
639 730
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 5f1c63ff9..f314d0ffe 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -85,7 +85,6 @@
85 85
86// Define extensions which must be supported. 86// Define extensions which must be supported.
87#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \ 87#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \
88 EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
89 EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \ 88 EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \
90 EXTENSION_NAME(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME) \ 89 EXTENSION_NAME(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME) \
91 EXTENSION_NAME(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME) \ 90 EXTENSION_NAME(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME) \
@@ -105,6 +104,7 @@
105 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \ 104 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \
106 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \ 105 EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
107 EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \ 106 EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \
107 EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
108 EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \ 108 EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \
109 EXTENSION_NAME(VK_NV_GEOMETRY_SHADER_PASSTHROUGH_EXTENSION_NAME) \ 109 EXTENSION_NAME(VK_NV_GEOMETRY_SHADER_PASSTHROUGH_EXTENSION_NAME) \
110 EXTENSION_NAME(VK_NV_VIEWPORT_ARRAY2_EXTENSION_NAME) \ 110 EXTENSION_NAME(VK_NV_VIEWPORT_ARRAY2_EXTENSION_NAME) \
@@ -141,9 +141,6 @@
141 FEATURE_NAME(features, vertexPipelineStoresAndAtomics) \ 141 FEATURE_NAME(features, vertexPipelineStoresAndAtomics) \
142 FEATURE_NAME(features, wideLines) \ 142 FEATURE_NAME(features, wideLines) \
143 FEATURE_NAME(host_query_reset, hostQueryReset) \ 143 FEATURE_NAME(host_query_reset, hostQueryReset) \
144 FEATURE_NAME(robustness2, nullDescriptor) \
145 FEATURE_NAME(robustness2, robustBufferAccess2) \
146 FEATURE_NAME(robustness2, robustImageAccess2) \
147 FEATURE_NAME(shader_demote_to_helper_invocation, shaderDemoteToHelperInvocation) \ 144 FEATURE_NAME(shader_demote_to_helper_invocation, shaderDemoteToHelperInvocation) \
148 FEATURE_NAME(shader_draw_parameters, shaderDrawParameters) \ 145 FEATURE_NAME(shader_draw_parameters, shaderDrawParameters) \
149 FEATURE_NAME(variable_pointer, variablePointers) \ 146 FEATURE_NAME(variable_pointer, variablePointers) \
@@ -156,6 +153,9 @@
156 FEATURE_NAME(index_type_uint8, indexTypeUint8) \ 153 FEATURE_NAME(index_type_uint8, indexTypeUint8) \
157 FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \ 154 FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \
158 FEATURE_NAME(provoking_vertex, provokingVertexLast) \ 155 FEATURE_NAME(provoking_vertex, provokingVertexLast) \
156 FEATURE_NAME(robustness2, nullDescriptor) \
157 FEATURE_NAME(robustness2, robustBufferAccess2) \
158 FEATURE_NAME(robustness2, robustImageAccess2) \
159 FEATURE_NAME(shader_float16_int8, shaderFloat16) \ 159 FEATURE_NAME(shader_float16_int8, shaderFloat16) \
160 FEATURE_NAME(shader_float16_int8, shaderInt8) \ 160 FEATURE_NAME(shader_float16_int8, shaderInt8) \
161 FEATURE_NAME(timeline_semaphore, timelineSemaphore) \ 161 FEATURE_NAME(timeline_semaphore, timelineSemaphore) \
@@ -295,6 +295,16 @@ public:
295 return features.features.textureCompressionASTC_LDR; 295 return features.features.textureCompressionASTC_LDR;
296 } 296 }
297 297
298 /// Returns true if descriptor aliasing is natively supported.
299 bool IsDescriptorAliasingSupported() const {
300 return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
301 }
302
303 /// Returns true if the device suppors float64 natively.
304 bool IsFloat64Supported() const {
305 return features.features.shaderFloat64;
306 }
307
298 /// Returns true if the device supports float16 natively. 308 /// Returns true if the device supports float16 natively.
299 bool IsFloat16Supported() const { 309 bool IsFloat16Supported() const {
300 return features.shader_float16_int8.shaderFloat16; 310 return features.shader_float16_int8.shaderFloat16;
@@ -495,6 +505,10 @@ public:
495 } 505 }
496 506
497 bool HasTimelineSemaphore() const { 507 bool HasTimelineSemaphore() const {
508 if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
509 // Timeline semaphores do not work properly on all Qualcomm drivers.
510 return false;
511 }
498 return features.timeline_semaphore.timelineSemaphore; 512 return features.timeline_semaphore.timelineSemaphore;
499 } 513 }
500 514
@@ -551,6 +565,10 @@ public:
551 return cant_blit_msaa; 565 return cant_blit_msaa;
552 } 566 }
553 567
568 bool MustEmulateScaledFormats() const {
569 return must_emulate_scaled_formats;
570 }
571
554 bool MustEmulateBGR565() const { 572 bool MustEmulateBGR565() const {
555 return must_emulate_bgr565; 573 return must_emulate_bgr565;
556 } 574 }
@@ -567,6 +585,10 @@ public:
567 return properties.properties.limits.maxVertexInputBindings; 585 return properties.properties.limits.maxVertexInputBindings;
568 } 586 }
569 587
588 bool SupportsConditionalBarriers() const {
589 return supports_conditional_barriers;
590 }
591
570private: 592private:
571 /// Checks if the physical device is suitable and configures the object state 593 /// Checks if the physical device is suitable and configures the object state
572 /// with all necessary info about its properties. 594 /// with all necessary info about its properties.
@@ -666,9 +688,11 @@ private:
666 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached 688 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
667 bool supports_d24_depth{}; ///< Supports D24 depth buffers. 689 bool supports_d24_depth{}; ///< Supports D24 depth buffers.
668 bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting. 690 bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
691 bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
669 bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format. 692 bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
670 bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3. 693 bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
671 bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3. 694 bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
695 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
672 u64 device_access_memory{}; ///< Total size of device local memory in bytes. 696 u64 device_access_memory{}; ///< Total size of device local memory in bytes.
673 u32 sets_per_pool{}; ///< Sets per Description Pool 697 u32 sets_per_pool{}; ///< Sets per Description Pool
674 698
diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp
index 4eb3913ee..47f6f2a03 100644
--- a/src/video_core/vulkan_common/vulkan_library.cpp
+++ b/src/video_core/vulkan_common/vulkan_library.cpp
@@ -10,29 +10,35 @@
10 10
11namespace Vulkan { 11namespace Vulkan {
12 12
13Common::DynamicLibrary OpenLibrary() { 13std::shared_ptr<Common::DynamicLibrary> OpenLibrary(
14 [[maybe_unused]] Core::Frontend::GraphicsContext* context) {
14 LOG_DEBUG(Render_Vulkan, "Looking for a Vulkan library"); 15 LOG_DEBUG(Render_Vulkan, "Looking for a Vulkan library");
15 Common::DynamicLibrary library; 16#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
17 // Android manages its Vulkan driver from the frontend.
18 return context->GetDriverLibrary();
19#else
20 auto library = std::make_shared<Common::DynamicLibrary>();
16#ifdef __APPLE__ 21#ifdef __APPLE__
17 // Check if a path to a specific Vulkan library has been specified. 22 // Check if a path to a specific Vulkan library has been specified.
18 char* const libvulkan_env = std::getenv("LIBVULKAN_PATH"); 23 char* const libvulkan_env = std::getenv("LIBVULKAN_PATH");
19 if (!libvulkan_env || !library.Open(libvulkan_env)) { 24 if (!libvulkan_env || !library->Open(libvulkan_env)) {
20 // Use the libvulkan.dylib from the application bundle. 25 // Use the libvulkan.dylib from the application bundle.
21 const auto filename = 26 const auto filename =
22 Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.dylib"; 27 Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.dylib";
23 void(library.Open(Common::FS::PathToUTF8String(filename).c_str())); 28 void(library->Open(Common::FS::PathToUTF8String(filename).c_str()));
24 } 29 }
25#else 30#else
26 std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); 31 std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
27 LOG_DEBUG(Render_Vulkan, "Trying Vulkan library: {}", filename); 32 LOG_DEBUG(Render_Vulkan, "Trying Vulkan library: {}", filename);
28 if (!library.Open(filename.c_str())) { 33 if (!library->Open(filename.c_str())) {
29 // Android devices may not have libvulkan.so.1, only libvulkan.so. 34 // Android devices may not have libvulkan.so.1, only libvulkan.so.
30 filename = Common::DynamicLibrary::GetVersionedFilename("vulkan"); 35 filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
31 LOG_DEBUG(Render_Vulkan, "Trying Vulkan library (second attempt): {}", filename); 36 LOG_DEBUG(Render_Vulkan, "Trying Vulkan library (second attempt): {}", filename);
32 void(library.Open(filename.c_str())); 37 void(library->Open(filename.c_str()));
33 } 38 }
34#endif 39#endif
35 return library; 40 return library;
41#endif
36} 42}
37 43
38} // namespace Vulkan 44} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_library.h b/src/video_core/vulkan_common/vulkan_library.h
index 364ca979b..e1734525e 100644
--- a/src/video_core/vulkan_common/vulkan_library.h
+++ b/src/video_core/vulkan_common/vulkan_library.h
@@ -3,10 +3,14 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <memory>
7
6#include "common/dynamic_library.h" 8#include "common/dynamic_library.h"
9#include "core/frontend/graphics_context.h"
7 10
8namespace Vulkan { 11namespace Vulkan {
9 12
10Common::DynamicLibrary OpenLibrary(); 13std::shared_ptr<Common::DynamicLibrary> OpenLibrary(
14 [[maybe_unused]] Core::Frontend::GraphicsContext* context = nullptr);
11 15
12} // namespace Vulkan 16} // namespace Vulkan
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 59d226113..cc6b6a25a 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -46,6 +46,7 @@
46#include "core/core.h" 46#include "core/core.h"
47#include "core/cpu_manager.h" 47#include "core/cpu_manager.h"
48#include "core/frontend/framebuffer_layout.h" 48#include "core/frontend/framebuffer_layout.h"
49#include "core/frontend/graphics_context.h"
49#include "input_common/drivers/camera.h" 50#include "input_common/drivers/camera.h"
50#include "input_common/drivers/keyboard.h" 51#include "input_common/drivers/keyboard.h"
51#include "input_common/drivers/mouse.h" 52#include "input_common/drivers/mouse.h"
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 6288fef62..bac9dff90 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -101,6 +101,12 @@ const std::map<Settings::RendererBackend, QString> Config::renderer_backend_text
101 {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))}, 101 {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
102}; 102};
103 103
104const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
105 {Settings::ShaderBackend::GLSL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
106 {Settings::ShaderBackend::GLASM, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
107 {Settings::ShaderBackend::SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
108};
109
104// This shouldn't have anything except static initializers (no functions). So 110// This shouldn't have anything except static initializers (no functions). So
105// QKeySequence(...).toString() is NOT ALLOWED HERE. 111// QKeySequence(...).toString() is NOT ALLOWED HERE.
106// This must be in alphabetical order according to action name as it must have the same order as 112// This must be in alphabetical order according to action name as it must have the same order as
@@ -754,6 +760,7 @@ void Config::ReadRendererValues() {
754 ReadGlobalSetting(Settings::values.use_fast_gpu_time); 760 ReadGlobalSetting(Settings::values.use_fast_gpu_time);
755 ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); 761 ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
756 ReadGlobalSetting(Settings::values.enable_compute_pipelines); 762 ReadGlobalSetting(Settings::values.enable_compute_pipelines);
763 ReadGlobalSetting(Settings::values.use_video_framerate);
757 ReadGlobalSetting(Settings::values.bg_red); 764 ReadGlobalSetting(Settings::values.bg_red);
758 ReadGlobalSetting(Settings::values.bg_green); 765 ReadGlobalSetting(Settings::values.bg_green);
759 ReadGlobalSetting(Settings::values.bg_blue); 766 ReadGlobalSetting(Settings::values.bg_blue);
@@ -1409,6 +1416,7 @@ void Config::SaveRendererValues() {
1409 WriteGlobalSetting(Settings::values.use_fast_gpu_time); 1416 WriteGlobalSetting(Settings::values.use_fast_gpu_time);
1410 WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); 1417 WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
1411 WriteGlobalSetting(Settings::values.enable_compute_pipelines); 1418 WriteGlobalSetting(Settings::values.enable_compute_pipelines);
1419 WriteGlobalSetting(Settings::values.use_video_framerate);
1412 WriteGlobalSetting(Settings::values.bg_red); 1420 WriteGlobalSetting(Settings::values.bg_red);
1413 WriteGlobalSetting(Settings::values.bg_green); 1421 WriteGlobalSetting(Settings::values.bg_green);
1414 WriteGlobalSetting(Settings::values.bg_blue); 1422 WriteGlobalSetting(Settings::values.bg_blue);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ad590ea9e..0fd4baf6b 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -54,6 +54,7 @@ public:
54 static const std::map<bool, QString> use_docked_mode_texts_map; 54 static const std::map<bool, QString> use_docked_mode_texts_map;
55 static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map; 55 static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map;
56 static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map; 56 static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
57 static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
57 58
58 static constexpr UISettings::Theme default_theme{ 59 static constexpr UISettings::Theme default_theme{
59#ifdef _WIN32 60#ifdef _WIN32
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index f316b598c..431585216 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -515,8 +515,8 @@ void ConfigureGraphics::RetrieveVulkanDevices() try {
515 auto wsi = QtCommon::GetWindowSystemInfo(window); 515 auto wsi = QtCommon::GetWindowSystemInfo(window);
516 516
517 vk::InstanceDispatch dld; 517 vk::InstanceDispatch dld;
518 const Common::DynamicLibrary library = OpenLibrary(); 518 const auto library = OpenLibrary();
519 const vk::Instance instance = CreateInstance(library, dld, VK_API_VERSION_1_1, wsi.type); 519 const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type);
520 const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices(); 520 const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices();
521 vk::SurfaceKHR surface = CreateSurface(instance, wsi); 521 vk::SurfaceKHR surface = CreateSurface(instance, wsi);
522 522
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index 896863f87..0463ac8b9 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -42,6 +42,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
42 Settings::values.use_vulkan_driver_pipeline_cache.GetValue()); 42 Settings::values.use_vulkan_driver_pipeline_cache.GetValue());
43 ui->enable_compute_pipelines_checkbox->setChecked( 43 ui->enable_compute_pipelines_checkbox->setChecked(
44 Settings::values.enable_compute_pipelines.GetValue()); 44 Settings::values.enable_compute_pipelines.GetValue());
45 ui->use_video_framerate_checkbox->setChecked(Settings::values.use_video_framerate.GetValue());
45 46
46 if (Settings::IsConfiguringGlobal()) { 47 if (Settings::IsConfiguringGlobal()) {
47 ui->gpu_accuracy->setCurrentIndex( 48 ui->gpu_accuracy->setCurrentIndex(
@@ -91,6 +92,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
91 ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines, 92 ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines,
92 ui->enable_compute_pipelines_checkbox, 93 ui->enable_compute_pipelines_checkbox,
93 enable_compute_pipelines); 94 enable_compute_pipelines);
95 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_video_framerate,
96 ui->use_video_framerate_checkbox, use_video_framerate);
94} 97}
95 98
96void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { 99void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -125,6 +128,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
125 Settings::values.max_anisotropy.UsingGlobal()); 128 Settings::values.max_anisotropy.UsingGlobal());
126 ui->enable_compute_pipelines_checkbox->setEnabled( 129 ui->enable_compute_pipelines_checkbox->setEnabled(
127 Settings::values.enable_compute_pipelines.UsingGlobal()); 130 Settings::values.enable_compute_pipelines.UsingGlobal());
131 ui->use_video_framerate_checkbox->setEnabled(
132 Settings::values.use_video_framerate.UsingGlobal());
128 133
129 return; 134 return;
130 } 135 }
@@ -149,6 +154,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
149 ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox, 154 ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox,
150 Settings::values.enable_compute_pipelines, 155 Settings::values.enable_compute_pipelines,
151 enable_compute_pipelines); 156 enable_compute_pipelines);
157 ConfigurationShared::SetColoredTristate(ui->use_video_framerate_checkbox,
158 Settings::values.use_video_framerate,
159 use_video_framerate);
152 ConfigurationShared::SetColoredComboBox( 160 ConfigurationShared::SetColoredComboBox(
153 ui->gpu_accuracy, ui->label_gpu_accuracy, 161 ui->gpu_accuracy, ui->label_gpu_accuracy,
154 static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); 162 static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 1c7b636b9..a4dc8ceb0 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -47,6 +47,7 @@ private:
47 ConfigurationShared::CheckState use_fast_gpu_time; 47 ConfigurationShared::CheckState use_fast_gpu_time;
48 ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache; 48 ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache;
49 ConfigurationShared::CheckState enable_compute_pipelines; 49 ConfigurationShared::CheckState enable_compute_pipelines;
50 ConfigurationShared::CheckState use_video_framerate;
50 51
51 const Core::System& system; 52 const Core::System& system;
52}; 53};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index 37757a918..e7f0ef6be 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -192,6 +192,16 @@ Compute pipelines are always enabled on all other drivers.</string>
192 </widget> 192 </widget>
193 </item> 193 </item>
194 <item> 194 <item>
195 <widget class="QCheckBox" name="use_video_framerate_checkbox">
196 <property name="toolTip">
197 <string>Run the game at normal speed during video playback, even when the framerate is unlocked.</string>
198 </property>
199 <property name="text">
200 <string>Sync to framerate of video playback</string>
201 </property>
202 </widget>
203 </item>
204 <item>
195 <widget class="QWidget" name="af_layout" native="true"> 205 <widget class="QWidget" name="af_layout" native="true">
196 <layout class="QHBoxLayout" name="horizontalLayout_1"> 206 <layout class="QHBoxLayout" name="horizontalLayout_1">
197 <property name="leftMargin"> 207 <property name="leftMargin">
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 82bce9a3a..013715b44 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -3067,7 +3067,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
3067 return false; 3067 return false;
3068 } 3068 }
3069 3069
3070 std::array<u8, 0x1000> buffer{}; 3070 std::vector<u8> buffer(1_MiB);
3071 3071
3072 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { 3072 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
3073 if (install_progress->wasCanceled()) { 3073 if (install_progress->wasCanceled()) {
@@ -3491,6 +3491,7 @@ void GMainWindow::ResetWindowSize1080() {
3491void GMainWindow::OnConfigure() { 3491void GMainWindow::OnConfigure() {
3492 const auto old_theme = UISettings::values.theme; 3492 const auto old_theme = UISettings::values.theme;
3493 const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); 3493 const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
3494 const auto old_language_index = Settings::values.language_index.GetValue();
3494 3495
3495 Settings::SetConfiguringGlobal(true); 3496 Settings::SetConfiguringGlobal(true);
3496 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system, 3497 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system,
@@ -3559,7 +3560,7 @@ void GMainWindow::OnConfigure() {
3559 emit UpdateThemedIcons(); 3560 emit UpdateThemedIcons();
3560 3561
3561 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 3562 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
3562 if (reload) { 3563 if (reload || Settings::values.language_index.GetValue() != old_language_index) {
3563 game_list->PopulateAsync(UISettings::values.game_dirs); 3564 game_list->PopulateAsync(UISettings::values.game_dirs);
3564 } 3565 }
3565 3566
@@ -4115,7 +4116,13 @@ void GMainWindow::UpdateDockedButton() {
4115void GMainWindow::UpdateAPIText() { 4116void GMainWindow::UpdateAPIText() {
4116 const auto api = Settings::values.renderer_backend.GetValue(); 4117 const auto api = Settings::values.renderer_backend.GetValue();
4117 const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second; 4118 const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second;
4118 renderer_status_button->setText(renderer_status_text.toUpper()); 4119 renderer_status_button->setText(
4120 api == Settings::RendererBackend::OpenGL
4121 ? tr("%1 %2").arg(
4122 renderer_status_text.toUpper(),
4123 Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue())
4124 ->second)
4125 : renderer_status_text.toUpper());
4119} 4126}
4120 4127
4121void GMainWindow::UpdateFilterText() { 4128void GMainWindow::UpdateFilterText() {
diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp
index 5e1f76339..6eefc94ed 100644
--- a/src/yuzu/startup_checks.cpp
+++ b/src/yuzu/startup_checks.cpp
@@ -25,9 +25,9 @@ void CheckVulkan() {
25 // Just start the Vulkan loader, this will crash if something is wrong 25 // Just start the Vulkan loader, this will crash if something is wrong
26 try { 26 try {
27 Vulkan::vk::InstanceDispatch dld; 27 Vulkan::vk::InstanceDispatch dld;
28 const Common::DynamicLibrary library = Vulkan::OpenLibrary(); 28 const auto library = Vulkan::OpenLibrary();
29 const Vulkan::vk::Instance instance = 29 const Vulkan::vk::Instance instance =
30 Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_1); 30 Vulkan::CreateInstance(*library, dld, VK_API_VERSION_1_1);
31 31
32 } catch (const Vulkan::vk::Exception& exception) { 32 } catch (const Vulkan::vk::Exception& exception) {
33 fmt::print(stderr, "Failed to initialize Vulkan: {}\n", exception.what()); 33 fmt::print(stderr, "Failed to initialize Vulkan: {}\n", exception.what());
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 644a30e59..911d461e4 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -318,7 +318,7 @@ anti_aliasing =
318fullscreen_mode = 318fullscreen_mode =
319 319
320# Aspect ratio 320# Aspect ratio
321# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Stretch to Window 321# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window
322aspect_ratio = 322aspect_ratio =
323 323
324# Anisotropic filtering 324# Anisotropic filtering
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index d9b453dee..4ad05e0e1 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -4,7 +4,9 @@
4#pragma once 4#pragma once
5 5
6#include <utility> 6#include <utility>
7
7#include "core/frontend/emu_window.h" 8#include "core/frontend/emu_window.h"
9#include "core/frontend/graphics_context.h"
8 10
9struct SDL_Window; 11struct SDL_Window;
10 12