summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt2
-rw-r--r--externals/CMakeLists.txt3
-rw-r--r--externals/nx_tzdb/CMakeLists.txt96
-rw-r--r--externals/nx_tzdb/NxTzdbCreateHeader.cmake8
-rw-r--r--externals/nx_tzdb/include/nx_tzdb.h2
m---------externals/nx_tzdb/tzdb_to_nx0
m---------externals/vcpkg0
-rw-r--r--src/android/app/build.gradle.kts32
-rw-r--r--src/android/app/src/main/AndroidManifest.xml3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt41
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt184
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt89
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt95
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt35
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt242
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt180
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt327
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt40
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt18
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt20
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt4
-rw-r--r--src/android/app/src/main/jni/native.cpp33
-rw-r--r--src/android/app/src/main/res/drawable/ic_pip_pause.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_pip_play.xml9
-rw-r--r--src/android/app/src/main/res/layout/activity_emulation.xml16
-rw-r--r--src/android/app/src/main/res/layout/fragment_emulation.xml82
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting_switch.xml55
-rw-r--r--src/android/app/src/main/res/layout/list_item_settings_header.xml28
-rw-r--r--src/android/app/src/main/res/navigation/emulation_navigation.xml18
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml14
-rw-r--r--src/android/app/src/main/res/values/arrays.xml23
-rw-r--r--src/android/app/src/main/res/values/integers.xml64
-rw-r--r--src/android/app/src/main/res/values/strings.xml32
-rw-r--r--src/android/build.gradle.kts9
-rw-r--r--src/common/fs/fs.cpp27
-rw-r--r--src/common/fs/fs_android.h5
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.cpp2
-rw-r--r--src/core/file_sys/vfs_concat.cpp14
-rw-r--r--src/core/file_sys/vfs_real.cpp187
-rw-r--r--src/core/file_sys/vfs_real.h30
-rw-r--r--src/core/hle/kernel/k_thread.cpp15
-rw-r--r--src/core/hle/kernel/k_thread.h4
-rw-r--r--src/core/hle/service/nfc/common/amiibo_crypto.cpp44
-rw-r--r--src/core/hle/service/nfc/common/amiibo_crypto.h9
-rw-r--r--src/core/hle/service/nfc/common/device.cpp164
-rw-r--r--src/core/hle/service/nfc/common/device.h11
-rw-r--r--src/core/hle/service/nfc/common/device_manager.cpp4
-rw-r--r--src/core/hle/service/nfc/mifare_result.h2
-rw-r--r--src/core/hle/service/nfc/nfc_interface.cpp13
-rw-r--r--src/core/hle/service/nfc/nfc_result.h3
-rw-r--r--src/core/hle/service/nfc/nfc_types.h37
-rw-r--r--src/core/hle/service/nfp/nfp_types.h26
-rw-r--r--src/core/hle/service/time/time_zone_manager.cpp11
-rw-r--r--src/core/hle/service/time/time_zone_service.cpp10
-rw-r--r--src/input_common/drivers/virtual_amiibo.cpp2
-rw-r--r--src/input_common/drivers/virtual_amiibo.h1
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h17
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h3
-rw-r--r--src/video_core/engines/draw_manager.cpp10
-rw-r--r--src/video_core/memory_manager.cpp4
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp19
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h6
-rw-r--r--src/video_core/renderer_opengl/gl_compute_pipeline.cpp23
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp27
-rw-r--r--src/video_core/renderer_opengl/gl_shader_context.h6
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp72
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h11
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h11
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp46
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp8
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp8
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp51
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h9
-rw-r--r--src/video_core/texture_cache/image_view_base.cpp52
-rw-r--r--src/video_core/texture_cache/image_view_base.h2
-rw-r--r--src/video_core/texture_cache/texture_cache.h52
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h22
-rw-r--r--src/video_core/textures/texture.cpp7
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp26
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h34
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp5
-rw-r--r--src/yuzu/configuration/configure_dialog.h7
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp55
-rw-r--r--src/yuzu/configuration/configure_graphics.h3
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp5
-rw-r--r--src/yuzu/configuration/configure_per_game.h3
-rw-r--r--src/yuzu/main.cpp8
-rw-r--r--src/yuzu/main.h6
-rw-r--r--src/yuzu/vk_device_info.cpp61
-rw-r--r--src/yuzu/vk_device_info.h36
-rw-r--r--vcpkg.json2
145 files changed, 2483 insertions, 1157 deletions
diff --git a/.gitignore b/.gitignore
index a5f7248c7..fbadb208b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,8 @@ CMakeSettings.json
26# OSX global filetypes 26# OSX global filetypes
27# Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) 27# Created by Finder or Spotlight in directories for various OS functionality (indexing, etc)
28.DS_Store 28.DS_Store
29.DS_Store?
30._*
29.AppleDouble 31.AppleDouble
30.LSOverride 32.LSOverride
31.Spotlight-V100 33.Spotlight-V100
diff --git a/.gitmodules b/.gitmodules
index 95eae8109..89f2ad924 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -52,3 +52,6 @@
52[submodule "libadrenotools"] 52[submodule "libadrenotools"]
53 path = externals/libadrenotools 53 path = externals/libadrenotools
54 url = https://github.com/bylaws/libadrenotools 54 url = https://github.com/bylaws/libadrenotools
55[submodule "tzdb_to_nx"]
56 path = externals/nx_tzdb/tzdb_to_nx
57 url = https://github.com/lat9nq/tzdb_to_nx.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3d03bbf94..6d3146c9e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -59,6 +59,8 @@ option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
59 59
60option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) 60option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
61 61
62option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
63
62CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) 64CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
63 65
64# On Android, fetch and compile libcxx before doing anything else 66# On Android, fetch and compile libcxx before doing anything else
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index e48137080..7cce27d51 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -63,8 +63,9 @@ if (YUZU_USE_EXTERNAL_SDL2)
63 # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers 63 # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
64 # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) 64 # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095)
65 # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) 65 # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
66 # CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809)
66 set(SDL_UNUSED_SUBSYSTEMS 67 set(SDL_UNUSED_SUBSYSTEMS
67 CPUinfo File Filesystem 68 File Filesystem
68 Locale Power Render) 69 Locale Power Render)
69 foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) 70 foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
70 string(TOUPPER ${_SUB} _OPT) 71 string(TOUPPER ${_SUB} _OPT)
diff --git a/externals/nx_tzdb/CMakeLists.txt b/externals/nx_tzdb/CMakeLists.txt
index 2f625c108..593786250 100644
--- a/externals/nx_tzdb/CMakeLists.txt
+++ b/externals/nx_tzdb/CMakeLists.txt
@@ -1,24 +1,60 @@
1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project 1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later 2# SPDX-License-Identifier: GPL-2.0-or-later
3 3
4set(NX_TZDB_VERSION "220816") 4set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include")
5set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip") 5
6add_library(nx_tzdb INTERFACE)
7
8find_program(GIT git)
9find_program(GNU_MAKE make)
10find_program(DATE_PROG date)
6 11
12set(CAN_BUILD_NX_TZDB true)
13
14if (NOT GIT)
15 set(CAN_BUILD_NX_TZDB false)
16endif()
17if (NOT GNU_MAKE)
18 set(CAN_BUILD_NX_TZDB false)
19endif()
20if (NOT DATE_PROG)
21 set(CAN_BUILD_NX_TZDB false)
22endif()
23if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
24 # tzdb_to_nx currently requires a posix-compliant host
25 # MinGW and Android are handled here due to the executable format being different from the host system
26 # TODO (lat9nq): cross-compiling support
27 set(CAN_BUILD_NX_TZDB false)
28endif()
29
30set(NX_TZDB_VERSION "220816")
7set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip") 31set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
8set(NX_TZDB_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
9 32
10set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include") 33set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
34
35if ((NOT CAN_BUILD_NX_TZDB OR YUZU_DOWNLOAD_TIME_ZONE_DATA) AND NOT EXISTS ${NX_TZDB_ARCHIVE})
36 set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
37
38 message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...")
39 file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE}
40 STATUS NX_TZDB_DOWNLOAD_STATUS)
41 list(GET NX_TZDB_DOWNLOAD_STATUS 0 NX_TZDB_DOWNLOAD_STATUS_CODE)
42 if (NOT NX_TZDB_DOWNLOAD_STATUS_CODE EQUAL 0)
43 message(FATAL_ERROR "Time zone data download failed (status code ${NX_TZDB_DOWNLOAD_STATUS_CODE})")
44 endif()
11 45
12if (NOT EXISTS ${NX_TZDB_ARCHIVE})
13 file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE})
14 file(ARCHIVE_EXTRACT 46 file(ARCHIVE_EXTRACT
15 INPUT 47 INPUT
16 ${NX_TZDB_ARCHIVE} 48 ${NX_TZDB_ARCHIVE}
17 DESTINATION 49 DESTINATION
18 ${NX_TZDB_DIR}) 50 ${NX_TZDB_ROMFS_DIR})
51elseif (CAN_BUILD_NX_TZDB AND NOT YUZU_DOWNLOAD_TIME_ZONE_DATA)
52 add_subdirectory(tzdb_to_nx)
53 add_dependencies(nx_tzdb x80e)
54
55 set(NX_TZDB_ROMFS_DIR "${NX_TZDB_DIR}")
19endif() 56endif()
20 57
21add_library(nx_tzdb INTERFACE)
22target_include_directories(nx_tzdb 58target_include_directories(nx_tzdb
23 INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include 59 INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
24 INTERFACE ${NX_TZDB_INCLUDE_DIR}) 60 INTERFACE ${NX_TZDB_INCLUDE_DIR})
@@ -41,25 +77,25 @@ function(CreateHeader ZONE_PATH HEADER_NAME)
41 target_sources(nx_tzdb PRIVATE ${HEADER_PATH}) 77 target_sources(nx_tzdb PRIVATE ${HEADER_PATH})
42endfunction() 78endfunction()
43 79
44CreateHeader(${NX_TZDB_DIR} base) 80CreateHeader(${NX_TZDB_ROMFS_DIR} base)
45CreateHeader(${NX_TZDB_DIR}/zoneinfo zoneinfo) 81CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo zoneinfo)
46CreateHeader(${NX_TZDB_DIR}/zoneinfo/Africa africa) 82CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Africa africa)
47CreateHeader(${NX_TZDB_DIR}/zoneinfo/America america) 83CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America america)
48CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Argentina america_argentina) 84CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Argentina america_argentina)
49CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Indiana america_indiana) 85CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Indiana america_indiana)
50CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Kentucky america_kentucky) 86CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Kentucky america_kentucky)
51CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/North_Dakota america_north_dakota) 87CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/North_Dakota america_north_dakota)
52CreateHeader(${NX_TZDB_DIR}/zoneinfo/Antartica antartica) 88CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Antarctica antarctica)
53CreateHeader(${NX_TZDB_DIR}/zoneinfo/Arctic arctic) 89CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Arctic arctic)
54CreateHeader(${NX_TZDB_DIR}/zoneinfo/Asia asia) 90CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Asia asia)
55CreateHeader(${NX_TZDB_DIR}/zoneinfo/Atlantic atlantic) 91CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Atlantic atlantic)
56CreateHeader(${NX_TZDB_DIR}/zoneinfo/Australia australia) 92CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Australia australia)
57CreateHeader(${NX_TZDB_DIR}/zoneinfo/Brazil brazil) 93CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Brazil brazil)
58CreateHeader(${NX_TZDB_DIR}/zoneinfo/Canada canada) 94CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Canada canada)
59CreateHeader(${NX_TZDB_DIR}/zoneinfo/Chile chile) 95CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Chile chile)
60CreateHeader(${NX_TZDB_DIR}/zoneinfo/Etc etc) 96CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Etc etc)
61CreateHeader(${NX_TZDB_DIR}/zoneinfo/Europe europe) 97CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Europe europe)
62CreateHeader(${NX_TZDB_DIR}/zoneinfo/Indian indian) 98CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Indian indian)
63CreateHeader(${NX_TZDB_DIR}/zoneinfo/Mexico mexico) 99CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Mexico mexico)
64CreateHeader(${NX_TZDB_DIR}/zoneinfo/Pacific pacific) 100CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Pacific pacific)
65CreateHeader(${NX_TZDB_DIR}/zoneinfo/US us) 101CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/US us)
diff --git a/externals/nx_tzdb/NxTzdbCreateHeader.cmake b/externals/nx_tzdb/NxTzdbCreateHeader.cmake
index 69166aa5b..8c29e1167 100644
--- a/externals/nx_tzdb/NxTzdbCreateHeader.cmake
+++ b/externals/nx_tzdb/NxTzdbCreateHeader.cmake
@@ -15,7 +15,7 @@ set(DIRECTORY_NAME ${HEADER_NAME})
15 15
16set(FILE_DATA "") 16set(FILE_DATA "")
17foreach(ZONE_FILE ${FILE_LIST}) 17foreach(ZONE_FILE ${FILE_LIST})
18 if ("${ZONE_FILE}" STREQUAL "\n") 18 if (ZONE_FILE STREQUAL "\n")
19 continue() 19 continue()
20 endif() 20 endif()
21 21
@@ -26,13 +26,13 @@ foreach(ZONE_FILE ${FILE_LIST})
26 foreach(I RANGE 0 ${ZONE_DATA_LEN} 2) 26 foreach(I RANGE 0 ${ZONE_DATA_LEN} 2)
27 math(EXPR BREAK_LINE "(${I} + 2) % 38") 27 math(EXPR BREAK_LINE "(${I} + 2) % 38")
28 28
29 string(SUBSTRING "${ZONE_DATA}" "${I}" "2" HEX_DATA) 29 string(SUBSTRING "${ZONE_DATA}" "${I}" 2 HEX_DATA)
30 if ("${HEX_DATA}" STREQUAL "") 30 if (NOT HEX_DATA)
31 break() 31 break()
32 endif() 32 endif()
33 33
34 string(APPEND FILE_DATA "0x${HEX_DATA},") 34 string(APPEND FILE_DATA "0x${HEX_DATA},")
35 if ("${BREAK_LINE}" STREQUAL "0") 35 if (BREAK_LINE EQUAL 0)
36 string(APPEND FILE_DATA "\n") 36 string(APPEND FILE_DATA "\n")
37 else() 37 else()
38 string(APPEND FILE_DATA " ") 38 string(APPEND FILE_DATA " ")
diff --git a/externals/nx_tzdb/include/nx_tzdb.h b/externals/nx_tzdb/include/nx_tzdb.h
index d7b1e4304..1f7c6069a 100644
--- a/externals/nx_tzdb/include/nx_tzdb.h
+++ b/externals/nx_tzdb/include/nx_tzdb.h
@@ -9,7 +9,7 @@
9#include "nx_tzdb/america_indiana.h" 9#include "nx_tzdb/america_indiana.h"
10#include "nx_tzdb/america_kentucky.h" 10#include "nx_tzdb/america_kentucky.h"
11#include "nx_tzdb/america_north_dakota.h" 11#include "nx_tzdb/america_north_dakota.h"
12#include "nx_tzdb/antartica.h" 12#include "nx_tzdb/antarctica.h"
13#include "nx_tzdb/arctic.h" 13#include "nx_tzdb/arctic.h"
14#include "nx_tzdb/asia.h" 14#include "nx_tzdb/asia.h"
15#include "nx_tzdb/atlantic.h" 15#include "nx_tzdb/atlantic.h"
diff --git a/externals/nx_tzdb/tzdb_to_nx b/externals/nx_tzdb/tzdb_to_nx
new file mode 160000
Subproject 8c272f21d19c6e821345fd055f41b9640f9189d
diff --git a/externals/vcpkg b/externals/vcpkg
Subproject a487471068f4cb1cbb4eeb340763cdcc0a75fd6 Subproject cbf56573a987527b39272e88cbdd11389b78c6e
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index fe613d339..bab4f4d0f 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -2,13 +2,17 @@
2// SPDX-License-Identifier: GPL-3.0-or-later 2// SPDX-License-Identifier: GPL-3.0-or-later
3 3
4import android.annotation.SuppressLint 4import android.annotation.SuppressLint
5import kotlin.collections.setOf
5import org.jetbrains.kotlin.konan.properties.Properties 6import org.jetbrains.kotlin.konan.properties.Properties
7import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
6 8
7plugins { 9plugins {
8 id("com.android.application") 10 id("com.android.application")
9 id("org.jetbrains.kotlin.android") 11 id("org.jetbrains.kotlin.android")
10 id("kotlin-parcelize") 12 id("kotlin-parcelize")
11 kotlin("plugin.serialization") version "1.8.21" 13 kotlin("plugin.serialization") version "1.8.21"
14 id("androidx.navigation.safeargs.kotlin")
15 id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
12} 16}
13 17
14/** 18/**
@@ -43,16 +47,6 @@ android {
43 jniLibs.useLegacyPackaging = true 47 jniLibs.useLegacyPackaging = true
44 } 48 }
45 49
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 { 50 defaultConfig {
57 // TODO If this is ever modified, change application_id in strings.xml 51 // TODO If this is ever modified, change application_id in strings.xml
58 applicationId = "org.yuzu.yuzu_emu" 52 applicationId = "org.yuzu.yuzu_emu"
@@ -166,6 +160,24 @@ android {
166 } 160 }
167} 161}
168 162
163tasks.getByPath("preBuild").dependsOn("ktlintCheck")
164
165ktlint {
166 version.set("0.47.1")
167 android.set(true)
168 ignoreFailures.set(false)
169 disabledRules.set(
170 setOf(
171 "no-wildcard-imports",
172 "package-name",
173 "import-ordering"
174 )
175 )
176 reporters {
177 reporter(ReporterType.CHECKSTYLE)
178 }
179}
180
169dependencies { 181dependencies {
170 implementation("androidx.core:core-ktx:1.10.1") 182 implementation("androidx.core:core-ktx:1.10.1")
171 implementation("androidx.appcompat:appcompat:1.6.1") 183 implementation("androidx.appcompat:appcompat:1.6.1")
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 55f62b4b9..e31ad69e2 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -53,8 +53,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
53 <activity 53 <activity
54 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" 54 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
55 android:theme="@style/Theme.Yuzu.Main" 55 android:theme="@style/Theme.Yuzu.Main"
56 android:launchMode="singleTop"
57 android:screenOrientation="userLandscape" 56 android:screenOrientation="userLandscape"
57 android:supportsPictureInPicture="true"
58 android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
58 android:exported="true"> 59 android:exported="true">
59 60
60 <intent-filter> 61 <intent-filter>
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
index 4be9ade14..f860cdd4b 100644
--- 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
@@ -14,16 +14,18 @@ import android.widget.TextView
14import androidx.annotation.Keep 14import androidx.annotation.Keep
15import androidx.fragment.app.DialogFragment 15import androidx.fragment.app.DialogFragment
16import com.google.android.material.dialog.MaterialAlertDialogBuilder 16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import java.lang.ref.WeakReference
17import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext 18import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
18import org.yuzu.yuzu_emu.activities.EmulationActivity 19import org.yuzu.yuzu_emu.activities.EmulationActivity
19import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath 20import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
21import org.yuzu.yuzu_emu.utils.FileUtil.exists
20import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize 22import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
23import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
21import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri 24import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
22import org.yuzu.yuzu_emu.utils.Log.error 25import org.yuzu.yuzu_emu.utils.Log.error
23import org.yuzu.yuzu_emu.utils.Log.verbose 26import org.yuzu.yuzu_emu.utils.Log.verbose
24import org.yuzu.yuzu_emu.utils.Log.warning 27import org.yuzu.yuzu_emu.utils.Log.warning
25import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 28import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
26import java.lang.ref.WeakReference
27 29
28/** 30/**
29 * Class which contains methods that interact 31 * Class which contains methods that interact
@@ -74,7 +76,9 @@ object NativeLibrary {
74 fun openContentUri(path: String?, openmode: String?): Int { 76 fun openContentUri(path: String?, openmode: String?): Int {
75 return if (isNativePath(path!!)) { 77 return if (isNativePath(path!!)) {
76 YuzuApplication.documentsTree!!.openContentUri(path, openmode) 78 YuzuApplication.documentsTree!!.openContentUri(path, openmode)
77 } else openContentUri(appContext, path, openmode) 79 } else {
80 openContentUri(appContext, path, openmode)
81 }
78 } 82 }
79 83
80 @Keep 84 @Keep
@@ -82,7 +86,29 @@ object NativeLibrary {
82 fun getSize(path: String?): Long { 86 fun getSize(path: String?): Long {
83 return if (isNativePath(path!!)) { 87 return if (isNativePath(path!!)) {
84 YuzuApplication.documentsTree!!.getFileSize(path) 88 YuzuApplication.documentsTree!!.getFileSize(path)
85 } else getFileSize(appContext, path) 89 } else {
90 getFileSize(appContext, path)
91 }
92 }
93
94 @Keep
95 @JvmStatic
96 fun exists(path: String?): Boolean {
97 return if (isNativePath(path!!)) {
98 YuzuApplication.documentsTree!!.exists(path)
99 } else {
100 exists(appContext, path)
101 }
102 }
103
104 @Keep
105 @JvmStatic
106 fun isDirectory(path: String?): Boolean {
107 return if (isNativePath(path!!)) {
108 YuzuApplication.documentsTree!!.isDirectory(path)
109 } else {
110 isDirectory(appContext, path)
111 }
86 } 112 }
87 113
88 /** 114 /**
@@ -283,6 +309,11 @@ object NativeLibrary {
283 external fun isRunning(): Boolean 309 external fun isRunning(): Boolean
284 310
285 /** 311 /**
312 * Returns true if emulation is paused.
313 */
314 external fun isPaused(): Boolean
315
316 /**
286 * Returns the performance stats for the current game 317 * Returns the performance stats for the current game
287 */ 318 */
288 external fun getPerfStats(): DoubleArray 319 external fun getPerfStats(): DoubleArray
@@ -431,7 +462,9 @@ object NativeLibrary {
431 Html.FROM_HTML_MODE_LEGACY 462 Html.FROM_HTML_MODE_LEGACY
432 ) 463 )
433 ) 464 )
434 .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() } 465 .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
466 emulationActivity.finish()
467 }
435 .setOnDismissListener { emulationActivity.finish() } 468 .setOnDismissListener { emulationActivity.finish() }
436 emulationActivity.runOnUiThread { 469 emulationActivity.runOnUiThread {
437 val alert = builder.create() 470 val alert = builder.create()
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
index 4c947b786..04ab6a220 100644
--- 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
@@ -7,12 +7,12 @@ import android.app.Application
7import android.app.NotificationChannel 7import android.app.NotificationChannel
8import android.app.NotificationManager 8import android.app.NotificationManager
9import android.content.Context 9import android.content.Context
10import java.io.File
10import org.yuzu.yuzu_emu.utils.DirectoryInitialization 11import org.yuzu.yuzu_emu.utils.DirectoryInitialization
11import org.yuzu.yuzu_emu.utils.DocumentsTree 12import org.yuzu.yuzu_emu.utils.DocumentsTree
12import org.yuzu.yuzu_emu.utils.GpuDriverHelper 13import org.yuzu.yuzu_emu.utils.GpuDriverHelper
13import java.io.File
14 14
15fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir 15fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
16 16
17class YuzuApplication : Application() { 17class YuzuApplication : Application() {
18 private fun createNotificationChannels() { 18 private fun createNotificationChannels() {
@@ -21,7 +21,9 @@ class YuzuApplication : Application() {
21 getString(R.string.emulation_notification_channel_name), 21 getString(R.string.emulation_notification_channel_name),
22 NotificationManager.IMPORTANCE_LOW 22 NotificationManager.IMPORTANCE_LOW
23 ) 23 )
24 emulationChannel.description = getString(R.string.emulation_notification_channel_description) 24 emulationChannel.description = getString(
25 R.string.emulation_notification_channel_description
26 )
25 emulationChannel.setSound(null, null) 27 emulationChannel.setSound(null, null)
26 emulationChannel.vibrationPattern = null 28 emulationChannel.vibrationPattern = null
27 29
@@ -48,7 +50,7 @@ class YuzuApplication : Application() {
48 GpuDriverHelper.initializeDriverParameters(applicationContext) 50 GpuDriverHelper.initializeDriverParameters(applicationContext)
49 NativeLibrary.logDeviceInfo() 51 NativeLibrary.logDeviceInfo()
50 52
51 createNotificationChannels(); 53 createNotificationChannels()
52 } 54 }
53 55
54 companion object { 56 companion object {
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
index 20a0394f5..f0a6753a9 100644
--- 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
@@ -4,14 +4,23 @@
4package org.yuzu.yuzu_emu.activities 4package org.yuzu.yuzu_emu.activities
5 5
6import android.app.Activity 6import android.app.Activity
7import android.app.PendingIntent
8import android.app.PictureInPictureParams
9import android.app.RemoteAction
10import android.content.BroadcastReceiver
7import android.content.Context 11import android.content.Context
8import android.content.Intent 12import android.content.Intent
13import android.content.IntentFilter
14import android.content.res.Configuration
9import android.graphics.Rect 15import android.graphics.Rect
16import android.graphics.drawable.Icon
10import android.hardware.Sensor 17import android.hardware.Sensor
11import android.hardware.SensorEvent 18import android.hardware.SensorEvent
12import android.hardware.SensorEventListener 19import android.hardware.SensorEventListener
13import android.hardware.SensorManager 20import android.hardware.SensorManager
21import android.os.Build
14import android.os.Bundle 22import android.os.Bundle
23import android.util.Rational
15import android.view.InputDevice 24import android.view.InputDevice
16import android.view.KeyEvent 25import android.view.KeyEvent
17import android.view.MotionEvent 26import android.view.MotionEvent
@@ -23,30 +32,27 @@ import androidx.appcompat.app.AppCompatActivity
23import androidx.core.view.WindowCompat 32import androidx.core.view.WindowCompat
24import androidx.core.view.WindowInsetsCompat 33import androidx.core.view.WindowInsetsCompat
25import androidx.core.view.WindowInsetsControllerCompat 34import androidx.core.view.WindowInsetsControllerCompat
26import androidx.lifecycle.Lifecycle 35import androidx.navigation.fragment.NavHostFragment
27import androidx.lifecycle.lifecycleScope 36import kotlin.math.roundToInt
28import androidx.lifecycle.repeatOnLifecycle
29import androidx.window.layout.WindowInfoTracker
30import kotlinx.coroutines.Dispatchers
31import kotlinx.coroutines.launch
32import org.yuzu.yuzu_emu.NativeLibrary 37import org.yuzu.yuzu_emu.NativeLibrary
33import org.yuzu.yuzu_emu.R 38import org.yuzu.yuzu_emu.R
39import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
40import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
41import org.yuzu.yuzu_emu.features.settings.model.IntSetting
34import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel 42import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
35import org.yuzu.yuzu_emu.fragments.EmulationFragment
36import org.yuzu.yuzu_emu.model.Game 43import org.yuzu.yuzu_emu.model.Game
37import org.yuzu.yuzu_emu.utils.ControllerMappingHelper 44import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
38import org.yuzu.yuzu_emu.utils.ForegroundService 45import org.yuzu.yuzu_emu.utils.ForegroundService
39import org.yuzu.yuzu_emu.utils.InputHandler 46import org.yuzu.yuzu_emu.utils.InputHandler
40import org.yuzu.yuzu_emu.utils.NfcReader 47import org.yuzu.yuzu_emu.utils.NfcReader
41import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
42import org.yuzu.yuzu_emu.utils.ThemeHelper 48import org.yuzu.yuzu_emu.utils.ThemeHelper
43import kotlin.math.roundToInt
44 49
45class EmulationActivity : AppCompatActivity(), SensorEventListener { 50class EmulationActivity : AppCompatActivity(), SensorEventListener {
51 private lateinit var binding: ActivityEmulationBinding
52
46 private var controllerMappingHelper: ControllerMappingHelper? = null 53 private var controllerMappingHelper: ControllerMappingHelper? = null
47 54
48 var isActivityRecreated = false 55 var isActivityRecreated = false
49 private var emulationFragment: EmulationFragment? = null
50 private lateinit var nfcReader: NfcReader 56 private lateinit var nfcReader: NfcReader
51 private lateinit var inputHandler: InputHandler 57 private lateinit var inputHandler: InputHandler
52 58
@@ -55,7 +61,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
55 private var motionTimestamp: Long = 0 61 private var motionTimestamp: Long = 0
56 private var flipMotionOrientation: Boolean = false 62 private var flipMotionOrientation: Boolean = false
57 63
58 private lateinit var game: Game 64 private val actionPause = "ACTION_EMULATOR_PAUSE"
65 private val actionPlay = "ACTION_EMULATOR_PLAY"
59 66
60 private val settingsViewModel: SettingsViewModel by viewModels() 67 private val settingsViewModel: SettingsViewModel by viewModels()
61 68
@@ -70,47 +77,31 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
70 settingsViewModel.settings.loadSettings() 77 settingsViewModel.settings.loadSettings()
71 78
72 super.onCreate(savedInstanceState) 79 super.onCreate(savedInstanceState)
73 if (savedInstanceState == null) { 80
74 // Get params we were passed 81 binding = ActivityEmulationBinding.inflate(layoutInflater)
75 game = intent.parcelable(EXTRA_SELECTED_GAME)!! 82 setContentView(binding.root)
76 isActivityRecreated = false 83
77 } else { 84 val navHostFragment =
78 isActivityRecreated = true 85 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
79 restoreState(savedInstanceState) 86 val navController = navHostFragment.navController
80 } 87 navController
88 .setGraph(R.navigation.emulation_navigation, intent.extras)
89
90 isActivityRecreated = savedInstanceState != null
91
81 controllerMappingHelper = ControllerMappingHelper() 92 controllerMappingHelper = ControllerMappingHelper()
82 93
83 // Set these options now so that the SurfaceView the game renders into is the right size. 94 // Set these options now so that the SurfaceView the game renders into is the right size.
84 enableFullscreenImmersive() 95 enableFullscreenImmersive()
85 96
86 setContentView(R.layout.activity_emulation)
87 window.decorView.setBackgroundColor(getColor(android.R.color.black)) 97 window.decorView.setBackgroundColor(getColor(android.R.color.black))
88 98
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) 99 nfcReader = NfcReader(this)
101 nfcReader.initialize() 100 nfcReader.initialize()
102 101
103 inputHandler = InputHandler() 102 inputHandler = InputHandler()
104 inputHandler.initialize() 103 inputHandler.initialize()
105 104
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 105 // Start a foreground service to prevent the app from getting killed in the background
115 val startIntent = Intent(this, ForegroundService::class.java) 106 val startIntent = Intent(this, ForegroundService::class.java)
116 startForegroundService(startIntent) 107 startForegroundService(startIntent)
@@ -143,6 +134,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
143 super.onResume() 134 super.onResume()
144 nfcReader.startScanning() 135 nfcReader.startScanning()
145 startMotionSensorListener() 136 startMotionSensorListener()
137
138 buildPictureInPictureParams()
146 } 139 }
147 140
148 override fun onPause() { 141 override fun onPause() {
@@ -151,17 +144,22 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
151 stopMotionSensorListener() 144 stopMotionSensorListener()
152 } 145 }
153 146
147 override fun onUserLeaveHint() {
148 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
149 if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) {
150 val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
151 .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
152 enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
153 }
154 }
155 }
156
154 override fun onNewIntent(intent: Intent) { 157 override fun onNewIntent(intent: Intent) {
155 super.onNewIntent(intent) 158 super.onNewIntent(intent)
156 setIntent(intent) 159 setIntent(intent)
157 nfcReader.onNewIntent(intent) 160 nfcReader.onNewIntent(intent)
158 } 161 }
159 162
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 { 163 override fun dispatchKeyEvent(event: KeyEvent): Boolean {
166 if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && 164 if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
167 event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD 165 event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
@@ -248,10 +246,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
248 246
249 override fun onAccuracyChanged(sensor: Sensor, i: Int) {} 247 override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
250 248
251 private fun restoreState(savedInstanceState: Bundle) {
252 game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
253 }
254
255 private fun enableFullscreenImmersive() { 249 private fun enableFullscreenImmersive() {
256 WindowCompat.setDecorFitsSystemWindows(window, false) 250 WindowCompat.setDecorFitsSystemWindows(window, false)
257 251
@@ -262,6 +256,100 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
262 } 256 }
263 } 257 }
264 258
259 private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder():
260 PictureInPictureParams.Builder {
261 val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) {
262 0 -> Rational(16, 9)
263 1 -> Rational(4, 3)
264 2 -> Rational(21, 9)
265 3 -> Rational(16, 10)
266 else -> null // Best fit
267 }
268 return this.apply { aspectRatio?.let { setAspectRatio(it) } }
269 }
270
271 private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder():
272 PictureInPictureParams.Builder {
273 val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf()
274 val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
275
276 if (NativeLibrary.isPaused()) {
277 val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play)
278 val playPendingIntent = PendingIntent.getBroadcast(
279 this@EmulationActivity,
280 R.drawable.ic_pip_play,
281 Intent(actionPlay),
282 pendingFlags
283 )
284 val playRemoteAction = RemoteAction(
285 playIcon,
286 getString(R.string.play),
287 getString(R.string.play),
288 playPendingIntent
289 )
290 pictureInPictureActions.add(playRemoteAction)
291 } else {
292 val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause)
293 val pausePendingIntent = PendingIntent.getBroadcast(
294 this@EmulationActivity,
295 R.drawable.ic_pip_pause,
296 Intent(actionPause),
297 pendingFlags
298 )
299 val pauseRemoteAction = RemoteAction(
300 pauseIcon,
301 getString(R.string.pause),
302 getString(R.string.pause),
303 pausePendingIntent
304 )
305 pictureInPictureActions.add(pauseRemoteAction)
306 }
307
308 return this.apply { setActions(pictureInPictureActions) }
309 }
310
311 fun buildPictureInPictureParams() {
312 val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
313 .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
314 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
315 pictureInPictureParamsBuilder.setAutoEnterEnabled(
316 BooleanSetting.PICTURE_IN_PICTURE.boolean
317 )
318 }
319 setPictureInPictureParams(pictureInPictureParamsBuilder.build())
320 }
321
322 private var pictureInPictureReceiver = object : BroadcastReceiver() {
323 override fun onReceive(context: Context?, intent: Intent) {
324 if (intent.action == actionPlay) {
325 if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation()
326 } else if (intent.action == actionPause) {
327 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
328 }
329 buildPictureInPictureParams()
330 }
331 }
332
333 override fun onPictureInPictureModeChanged(
334 isInPictureInPictureMode: Boolean,
335 newConfig: Configuration
336 ) {
337 super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
338 if (isInPictureInPictureMode) {
339 IntentFilter().apply {
340 addAction(actionPause)
341 addAction(actionPlay)
342 }.also {
343 registerReceiver(pictureInPictureReceiver, it)
344 }
345 } else {
346 try {
347 unregisterReceiver(pictureInPictureReceiver)
348 } catch (ignored: Exception) {
349 }
350 }
351 }
352
265 private fun startMotionSensorListener() { 353 private fun startMotionSensorListener() {
266 val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager 354 val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
267 val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) 355 val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
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
index 7f9e2e2d4..e91277d35 100644
--- 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
@@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity
16import androidx.documentfile.provider.DocumentFile 16import androidx.documentfile.provider.DocumentFile
17import androidx.lifecycle.ViewModelProvider 17import androidx.lifecycle.ViewModelProvider
18import androidx.lifecycle.lifecycleScope 18import androidx.lifecycle.lifecycleScope
19import androidx.navigation.findNavController
19import androidx.preference.PreferenceManager 20import androidx.preference.PreferenceManager
20import androidx.recyclerview.widget.AsyncDifferConfig 21import androidx.recyclerview.widget.AsyncDifferConfig
21import androidx.recyclerview.widget.DiffUtil 22import androidx.recyclerview.widget.DiffUtil
@@ -23,13 +24,13 @@ import androidx.recyclerview.widget.ListAdapter
23import androidx.recyclerview.widget.RecyclerView 24import androidx.recyclerview.widget.RecyclerView
24import coil.load 25import coil.load
25import kotlinx.coroutines.launch 26import kotlinx.coroutines.launch
27import org.yuzu.yuzu_emu.HomeNavigationDirections
26import org.yuzu.yuzu_emu.NativeLibrary 28import org.yuzu.yuzu_emu.NativeLibrary
27import org.yuzu.yuzu_emu.R 29import org.yuzu.yuzu_emu.R
28import org.yuzu.yuzu_emu.YuzuApplication 30import org.yuzu.yuzu_emu.YuzuApplication
31import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
29import org.yuzu.yuzu_emu.databinding.CardGameBinding 32import org.yuzu.yuzu_emu.databinding.CardGameBinding
30import org.yuzu.yuzu_emu.activities.EmulationActivity
31import org.yuzu.yuzu_emu.model.Game 33import org.yuzu.yuzu_emu.model.Game
32import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
33import org.yuzu.yuzu_emu.model.GamesViewModel 34import org.yuzu.yuzu_emu.model.GamesViewModel
34 35
35class GameAdapter(private val activity: AppCompatActivity) : 36class GameAdapter(private val activity: AppCompatActivity) :
@@ -58,7 +59,10 @@ class GameAdapter(private val activity: AppCompatActivity) :
58 override fun onClick(view: View) { 59 override fun onClick(view: View) {
59 val holder = view.tag as GameViewHolder 60 val holder = view.tag as GameViewHolder
60 61
61 val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true 62 val gameExists = DocumentFile.fromSingleUri(
63 YuzuApplication.appContext,
64 Uri.parse(holder.game.path)
65 )?.exists() == true
62 if (!gameExists) { 66 if (!gameExists) {
63 Toast.makeText( 67 Toast.makeText(
64 YuzuApplication.appContext, 68 YuzuApplication.appContext,
@@ -78,7 +82,8 @@ class GameAdapter(private val activity: AppCompatActivity) :
78 ) 82 )
79 .apply() 83 .apply()
80 84
81 EmulationActivity.launch(activity, holder.game) 85 val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
86 view.findNavController().navigate(action)
82 } 87 }
83 88
84 inner class GameViewHolder(val binding: CardGameBinding) : 89 inner class GameViewHolder(val binding: CardGameBinding) :
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
index b719dd539..d3df3bc81 100644
--- 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
@@ -58,11 +58,12 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
58 ) 58 )
59 59
60 when (option.titleId) { 60 when (option.titleId) {
61 R.string.get_early_access -> binding.optionLayout.background = 61 R.string.get_early_access ->
62 ContextCompat.getDrawable( 62 binding.optionLayout.background =
63 binding.optionCard.context, 63 ContextCompat.getDrawable(
64 R.drawable.premium_background 64 binding.optionCard.context,
65 ) 65 R.drawable.premium_background
66 )
66 } 67 }
67 } 68 }
68 } 69 }
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
index 82a6712b6..e058067c9 100644
--- 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
@@ -12,10 +12,10 @@ import android.view.WindowInsets
12import android.view.inputmethod.InputMethodManager 12import android.view.inputmethod.InputMethodManager
13import androidx.annotation.Keep 13import androidx.annotation.Keep
14import androidx.core.view.ViewCompat 14import androidx.core.view.ViewCompat
15import java.io.Serializable
15import org.yuzu.yuzu_emu.NativeLibrary 16import org.yuzu.yuzu_emu.NativeLibrary
16import org.yuzu.yuzu_emu.R 17import org.yuzu.yuzu_emu.R
17import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment 18import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment
18import java.io.Serializable
19 19
20@Keep 20@Keep
21object SoftwareKeyboard { 21object SoftwareKeyboard {
@@ -40,19 +40,22 @@ object SoftwareKeyboard {
40 // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. 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()!!) 41 val handler = Handler(Looper.myLooper()!!)
42 val delayMs = 500 42 val delayMs = 500
43 handler.postDelayed(object : Runnable { 43 handler.postDelayed(
44 override fun run() { 44 object : Runnable {
45 val insets = ViewCompat.getRootWindowInsets(overlayView) 45 override fun run() {
46 val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) 46 val insets = ViewCompat.getRootWindowInsets(overlayView)
47 if (isKeyboardVisible) { 47 val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
48 handler.postDelayed(this, delayMs.toLong()) 48 if (isKeyboardVisible) {
49 return 49 handler.postDelayed(this, delayMs.toLong())
50 } 50 return
51 }
51 52
52 // No longer visible, submit the result. 53 // No longer visible, submit the result.
53 NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) 54 NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
54 } 55 }
55 }, delayMs.toLong()) 56 },
57 delayMs.toLong()
58 )
56 } 59 }
57 60
58 @JvmStatic 61 @JvmStatic
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
index 3b1559c80..a18efef19 100644
--- 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
@@ -20,7 +20,10 @@ object DiskShaderCacheProgress {
20 emulationActivity.getString(R.string.loading), 20 emulationActivity.getString(R.string.loading),
21 emulationActivity.getString(R.string.preparing_shaders) 21 emulationActivity.getString(R.string.preparing_shaders)
22 ) 22 )
23 fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG) 23 fragment.show(
24 emulationActivity.supportFragmentManager,
25 ShaderProgressDialogFragment.TAG
26 )
24 } 27 }
25 synchronized(finishLock) { finishLock.wait() } 28 synchronized(finishLock) { finishLock.wait() }
26 } 29 }
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
index 2c68c9ac3..8a8e0a6e8 100644
--- 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
@@ -62,7 +62,9 @@ class ShaderProgressDialogFragment : DialogFragment() {
62 shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> 62 shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
63 alertDialog.setMessage(msg) 63 alertDialog.setMessage(msg)
64 } 64 }
65 synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() } 65 synchronized(DiskShaderCacheProgress.finishLock) {
66 DiskShaderCacheProgress.finishLock.notifyAll()
67 }
66 } 68 }
67 69
68 override fun onDestroyView() { 70 override fun onDestroyView() {
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
index 4c3a9ca80..f3be156b5 100644
--- 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
@@ -13,11 +13,11 @@ import android.os.ParcelFileDescriptor
13import android.provider.DocumentsContract 13import android.provider.DocumentsContract
14import android.provider.DocumentsProvider 14import android.provider.DocumentsProvider
15import android.webkit.MimeTypeMap 15import android.webkit.MimeTypeMap
16import java.io.*
16import org.yuzu.yuzu_emu.BuildConfig 17import org.yuzu.yuzu_emu.BuildConfig
17import org.yuzu.yuzu_emu.R 18import org.yuzu.yuzu_emu.R
18import org.yuzu.yuzu_emu.YuzuApplication 19import org.yuzu.yuzu_emu.YuzuApplication
19import org.yuzu.yuzu_emu.getPublicFilesDir 20import org.yuzu.yuzu_emu.getPublicFilesDir
20import java.io.*
21 21
22class DocumentProvider : DocumentsProvider() { 22class DocumentProvider : DocumentsProvider() {
23 private val baseDirectory: File 23 private val baseDirectory: File
@@ -44,7 +44,7 @@ class DocumentProvider : DocumentsProvider() {
44 DocumentsContract.Document.COLUMN_SIZE 44 DocumentsContract.Document.COLUMN_SIZE
45 ) 45 )
46 46
47 const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user" 47 const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".user"
48 const val ROOT_ID: String = "root" 48 const val ROOT_ID: String = "root"
49 } 49 }
50 50
@@ -58,7 +58,11 @@ class DocumentProvider : DocumentsProvider() {
58 private fun getFile(documentId: String): File { 58 private fun getFile(documentId: String): File {
59 if (documentId.startsWith(ROOT_ID)) { 59 if (documentId.startsWith(ROOT_ID)) {
60 val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) 60 val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
61 if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found") 61 if (!file.exists()) {
62 throw FileNotFoundException(
63 "${file.absolutePath} ($documentId) not found"
64 )
65 }
62 return file 66 return file
63 } else { 67 } else {
64 throw FileNotFoundException("'$documentId' is not in any known root") 68 throw FileNotFoundException("'$documentId' is not in any known root")
@@ -80,7 +84,8 @@ class DocumentProvider : DocumentsProvider() {
80 add(DocumentsContract.Root.COLUMN_SUMMARY, null) 84 add(DocumentsContract.Root.COLUMN_SUMMARY, null)
81 add( 85 add(
82 DocumentsContract.Root.COLUMN_FLAGS, 86 DocumentsContract.Root.COLUMN_FLAGS,
83 DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD 87 DocumentsContract.Root.FLAG_SUPPORTS_CREATE or
88 DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
84 ) 89 )
85 add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name)) 90 add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name))
86 add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) 91 add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
@@ -127,11 +132,13 @@ class DocumentProvider : DocumentsProvider() {
127 132
128 try { 133 try {
129 if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { 134 if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {
130 if (!newFile.mkdir()) 135 if (!newFile.mkdir()) {
131 throw IOException("Failed to create directory") 136 throw IOException("Failed to create directory")
137 }
132 } else { 138 } else {
133 if (!newFile.createNewFile()) 139 if (!newFile.createNewFile()) {
134 throw IOException("Failed to create file") 140 throw IOException("Failed to create file")
141 }
135 } 142 }
136 } catch (e: IOException) { 143 } catch (e: IOException) {
137 throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") 144 throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
@@ -142,8 +149,9 @@ class DocumentProvider : DocumentsProvider() {
142 149
143 override fun deleteDocument(documentId: String?) { 150 override fun deleteDocument(documentId: String?) {
144 val file = getFile(documentId!!) 151 val file = getFile(documentId!!)
145 if (!file.delete()) 152 if (!file.delete()) {
146 throw FileNotFoundException("Couldn't delete document with ID '$documentId'") 153 throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
154 }
147 } 155 }
148 156
149 override fun removeDocument(documentId: String, parentDocumentId: String?) { 157 override fun removeDocument(documentId: String, parentDocumentId: String?) {
@@ -151,38 +159,55 @@ class DocumentProvider : DocumentsProvider() {
151 val file = getFile(documentId) 159 val file = getFile(documentId)
152 160
153 if (parent == file || file.parentFile == null || file.parentFile!! == parent) { 161 if (parent == file || file.parentFile == null || file.parentFile!! == parent) {
154 if (!file.delete()) 162 if (!file.delete()) {
155 throw FileNotFoundException("Couldn't delete document with ID '$documentId'") 163 throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
164 }
156 } else { 165 } else {
157 throw FileNotFoundException("Couldn't delete document with ID '$documentId'") 166 throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
158 } 167 }
159 } 168 }
160 169
161 override fun renameDocument(documentId: String?, displayName: String?): String { 170 override fun renameDocument(documentId: String?, displayName: String?): String {
162 if (displayName == null) 171 if (displayName == null) {
163 throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null") 172 throw FileNotFoundException(
173 "Couldn't rename document '$documentId' as the new name is null"
174 )
175 }
164 176
165 val sourceFile = getFile(documentId!!) 177 val sourceFile = getFile(documentId!!)
166 val sourceParentFile = sourceFile.parentFile 178 val sourceParentFile = sourceFile.parentFile
167 ?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent") 179 ?: throw FileNotFoundException(
180 "Couldn't rename document '$documentId' as it has no parent"
181 )
168 val destFile = sourceParentFile.resolve(displayName) 182 val destFile = sourceParentFile.resolve(displayName)
169 183
170 try { 184 try {
171 if (!sourceFile.renameTo(destFile)) 185 if (!sourceFile.renameTo(destFile)) {
172 throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'") 186 throw FileNotFoundException(
187 "Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'"
188 )
189 }
173 } catch (e: Exception) { 190 } catch (e: Exception) {
174 throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}") 191 throw FileNotFoundException(
192 "Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': " +
193 "${e.message}"
194 )
175 } 195 }
176 196
177 return getDocumentId(destFile) 197 return getDocumentId(destFile)
178 } 198 }
179 199
180 private fun copyDocument( 200 private fun copyDocument(
181 sourceDocumentId: String, sourceParentDocumentId: String, 201 sourceDocumentId: String,
202 sourceParentDocumentId: String,
182 targetParentDocumentId: String? 203 targetParentDocumentId: String?
183 ): String { 204 ): String {
184 if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) 205 if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) {
185 throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'") 206 throw FileNotFoundException(
207 "Couldn't copy document '$sourceDocumentId' as its parent is not " +
208 "'$sourceParentDocumentId'"
209 )
210 }
186 211
187 return copyDocument(sourceDocumentId, targetParentDocumentId) 212 return copyDocument(sourceDocumentId, targetParentDocumentId)
188 } 213 }
@@ -193,8 +218,13 @@ class DocumentProvider : DocumentsProvider() {
193 val newFile = parent.resolveWithoutConflict(oldFile.name) 218 val newFile = parent.resolveWithoutConflict(oldFile.name)
194 219
195 try { 220 try {
196 if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true))) 221 if (!(
222 newFile.createNewFile() && newFile.setWritable(true) &&
223 newFile.setReadable(true)
224 )
225 ) {
197 throw IOException("Couldn't create new file") 226 throw IOException("Couldn't create new file")
227 }
198 228
199 FileInputStream(oldFile).use { inStream -> 229 FileInputStream(oldFile).use { inStream ->
200 FileOutputStream(newFile).use { outStream -> 230 FileOutputStream(newFile).use { outStream ->
@@ -209,12 +239,14 @@ class DocumentProvider : DocumentsProvider() {
209 } 239 }
210 240
211 override fun moveDocument( 241 override fun moveDocument(
212 sourceDocumentId: String, sourceParentDocumentId: String?, 242 sourceDocumentId: String,
243 sourceParentDocumentId: String?,
213 targetParentDocumentId: String? 244 targetParentDocumentId: String?
214 ): String { 245 ): String {
215 try { 246 try {
216 val newDocumentId = copyDocument( 247 val newDocumentId = copyDocument(
217 sourceDocumentId, sourceParentDocumentId!!, 248 sourceDocumentId,
249 sourceParentDocumentId!!,
218 targetParentDocumentId 250 targetParentDocumentId
219 ) 251 )
220 removeDocument(sourceDocumentId, sourceParentDocumentId) 252 removeDocument(sourceDocumentId, sourceParentDocumentId)
@@ -245,24 +277,30 @@ class DocumentProvider : DocumentsProvider() {
245 add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) 277 add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
246 add( 278 add(
247 DocumentsContract.Document.COLUMN_DISPLAY_NAME, 279 DocumentsContract.Document.COLUMN_DISPLAY_NAME,
248 if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name 280 if (localFile == baseDirectory) {
281 context!!.getString(R.string.app_name)
282 } else {
283 localFile.name
284 }
249 ) 285 )
250 add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) 286 add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
251 add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) 287 add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
252 add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) 288 add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
253 add(DocumentsContract.Document.COLUMN_FLAGS, flags) 289 add(DocumentsContract.Document.COLUMN_FLAGS, flags)
254 if (localFile == baseDirectory) 290 if (localFile == baseDirectory) {
255 add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu) 291 add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu)
292 }
256 } 293 }
257 294
258 return cursor 295 return cursor
259 } 296 }
260 297
261 private fun getTypeForFile(file: File): Any { 298 private fun getTypeForFile(file: File): Any {
262 return if (file.isDirectory) 299 return if (file.isDirectory) {
263 DocumentsContract.Document.MIME_TYPE_DIR 300 DocumentsContract.Document.MIME_TYPE_DIR
264 else 301 } else {
265 getTypeForName(file.name) 302 getTypeForName(file.name)
303 }
266 } 304 }
267 305
268 private fun getTypeForName(name: String): Any { 306 private fun getTypeForName(name: String): Any {
@@ -270,8 +308,9 @@ class DocumentProvider : DocumentsProvider() {
270 if (lastDot >= 0) { 308 if (lastDot >= 0) {
271 val extension = name.substring(lastDot + 1) 309 val extension = name.substring(lastDot + 1)
272 val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) 310 val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
273 if (mime != null) 311 if (mime != null) {
274 return mime 312 return mime
313 }
275 } 314 }
276 return "application/octect-stream" 315 return "application/octect-stream"
277 } 316 }
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
index 3dfd66779..d41933766 100644
--- 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
@@ -8,6 +8,10 @@ enum class BooleanSetting(
8 override val section: String, 8 override val section: String,
9 override val defaultValue: Boolean 9 override val defaultValue: Boolean
10) : AbstractBooleanSetting { 10) : AbstractBooleanSetting {
11 CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
12 FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
13 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
14 PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
11 USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); 15 USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
12 16
13 override var boolean: Boolean = defaultValue 17 override var boolean: Boolean = defaultValue
@@ -27,6 +31,7 @@ enum class BooleanSetting(
27 31
28 companion object { 32 companion object {
29 private val NOT_RUNTIME_EDITABLE = listOf( 33 private val NOT_RUNTIME_EDITABLE = listOf(
34 PICTURE_IN_PICTURE,
30 USE_CUSTOM_RTC 35 USE_CUSTOM_RTC
31 ) 36 )
32 37
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
index fa84f94f5..4427a7d9d 100644
--- 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
@@ -93,6 +93,11 @@ enum class IntSetting(
93 Settings.SECTION_RENDERER, 93 Settings.SECTION_RENDERER,
94 0 94 0
95 ), 95 ),
96 RENDERER_SCREEN_LAYOUT(
97 "screen_layout",
98 Settings.SECTION_RENDERER,
99 Settings.LayoutOption_MobileLandscape
100 ),
96 RENDERER_ASPECT_RATIO( 101 RENDERER_ASPECT_RATIO(
97 "aspect_ratio", 102 "aspect_ratio",
98 Settings.SECTION_RENDERER, 103 Settings.SECTION_RENDERER,
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
index 8df20b928..88afb2223 100644
--- 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
@@ -4,11 +4,11 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import android.text.TextUtils 6import android.text.TextUtils
7import java.util.*
7import org.yuzu.yuzu_emu.R 8import org.yuzu.yuzu_emu.R
8import org.yuzu.yuzu_emu.YuzuApplication 9import org.yuzu.yuzu_emu.YuzuApplication
9import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView 10import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
10import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 11import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
11import java.util.*
12 12
13class Settings { 13class Settings {
14 private var gameId: String? = null 14 private var gameId: String? = null
@@ -133,7 +133,6 @@ class Settings {
133 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" 133 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
134 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" 134 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
135 const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" 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" 136 const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
138 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" 137 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
139 138
@@ -144,6 +143,10 @@ class Settings {
144 143
145 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() 144 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
146 145
146 const val LayoutOption_Unspecified = 0
147 const val LayoutOption_MobilePortrait = 4
148 const val LayoutOption_MobileLandscape = 5
149
147 init { 150 init {
148 configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = 151 configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
149 listOf( 152 listOf(
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
index 63f95690c..6621289fd 100644
--- 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
@@ -8,6 +8,7 @@ enum class StringSetting(
8 override val section: String, 8 override val section: String,
9 override val defaultValue: String 9 override val defaultValue: String
10) : AbstractStringSetting { 10) : AbstractStringSetting {
11 AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
11 CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); 12 CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
12 13
13 override var string: String = defaultValue 14 override var string: String = defaultValue
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
index 0f8edbfb0..a67001311 100644
--- 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
@@ -3,12 +3,8 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7
8class HeaderSetting( 6class HeaderSetting(
9 setting: AbstractSetting?, 7 titleId: Int
10 titleId: Int, 8) : SettingsItem(null, titleId, 0) {
11 descriptionId: Int
12) : SettingsItem(setting, titleId, descriptionId) {
13 override val type = TYPE_HEADER 9 override val type = TYPE_HEADER
14} 10}
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
index 9eac9904e..7306ec458 100644
--- 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
@@ -4,7 +4,6 @@
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting 6import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
7import org.yuzu.yuzu_emu.features.settings.model.IntSetting
8 7
9class SingleChoiceSetting( 8class SingleChoiceSetting(
10 setting: AbstractIntSetting?, 9 setting: AbstractIntSetting?,
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
index 842648ce4..92d0167ae 100644
--- 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
@@ -3,13 +3,11 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import kotlin.math.roundToInt
6import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting 7import 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.AbstractIntSetting
8import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 9import 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 10import org.yuzu.yuzu_emu.utils.Log
12import kotlin.math.roundToInt
13 11
14class SliderSetting( 12class SliderSetting(
15 setting: AbstractSetting?, 13 setting: AbstractSetting?,
@@ -19,7 +17,7 @@ class SliderSetting(
19 val max: Int, 17 val max: Int,
20 val units: String, 18 val units: String,
21 val key: String? = null, 19 val key: String? = null,
22 val defaultValue: Int? = null, 20 val defaultValue: Int? = null
23) : SettingsItem(setting, titleId, descriptionId) { 21) : SettingsItem(setting, titleId, descriptionId) {
24 override val type = TYPE_SLIDER 22 override val type = TYPE_SLIDER
25 23
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
index 9e9b00d10..3b6731dcd 100644
--- 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
@@ -5,24 +5,25 @@ package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting 7import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
8import org.yuzu.yuzu_emu.features.settings.model.StringSetting
9 8
10class StringSingleChoiceSetting( 9class StringSingleChoiceSetting(
11 val key: String? = null,
12 setting: AbstractSetting?, 10 setting: AbstractSetting?,
13 titleId: Int, 11 titleId: Int,
14 descriptionId: Int, 12 descriptionId: Int,
15 val choicesId: Array<String>, 13 val choices: Array<String>,
16 private val valuesId: Array<String>?, 14 val values: Array<String>?,
15 val key: String? = null,
17 private val defaultValue: String? = null 16 private val defaultValue: String? = null
18) : SettingsItem(setting, titleId, descriptionId) { 17) : SettingsItem(setting, titleId, descriptionId) {
19 override val type = TYPE_STRING_SINGLE_CHOICE 18 override val type = TYPE_STRING_SINGLE_CHOICE
20 19
21 fun getValueAt(index: Int): String? { 20 fun getValueAt(index: Int): String? {
22 if (valuesId == null) return null 21 if (values == null) return null
23 return if (index >= 0 && index < valuesId.size) { 22 return if (index >= 0 && index < values.size) {
24 valuesId[index] 23 values[index]
25 } else "" 24 } else {
25 ""
26 }
26 } 27 }
27 28
28 val selectedValue: String 29 val selectedValue: String
@@ -35,8 +36,8 @@ class StringSingleChoiceSetting(
35 val selectValueIndex: Int 36 val selectValueIndex: Int
36 get() { 37 get() {
37 val selectedValue = selectedValue 38 val selectedValue = selectedValue
38 for (i in valuesId!!.indices) { 39 for (i in values!!.indices) {
39 if (valuesId[i] == selectedValue) { 40 if (values[i] == selectedValue) {
40 return i 41 return i
41 } 42 }
42 } 43 }
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
index a3ef59c2f..8a9d13a92 100644
--- 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
@@ -3,8 +3,6 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7
8class SubmenuSetting( 6class SubmenuSetting(
9 titleId: Int, 7 titleId: Int,
10 descriptionId: Int, 8 descriptionId: Int,
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
index 72e2cce2a..a5af5a7ae 100644
--- 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
@@ -8,17 +8,18 @@ import android.content.Intent
8import android.os.Bundle 8import android.os.Bundle
9import android.view.Menu 9import android.view.Menu
10import android.view.View 10import android.view.View
11import android.view.ViewGroup.MarginLayoutParams
11import android.widget.Toast 12import android.widget.Toast
13import androidx.activity.OnBackPressedCallback
14import androidx.activity.result.ActivityResultLauncher
12import androidx.activity.viewModels 15import androidx.activity.viewModels
13import androidx.appcompat.app.AppCompatActivity 16import androidx.appcompat.app.AppCompatActivity
14import androidx.core.view.ViewCompat 17import androidx.core.view.ViewCompat
15import androidx.core.view.WindowCompat 18import androidx.core.view.WindowCompat
16import androidx.core.view.WindowInsetsCompat 19import androidx.core.view.WindowInsetsCompat
17import android.view.ViewGroup.MarginLayoutParams
18import androidx.activity.OnBackPressedCallback
19import androidx.core.view.updatePadding 20import androidx.core.view.updatePadding
20import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
21import org.yuzu.yuzu_emu.NativeLibrary 22import java.io.IOException
22import org.yuzu.yuzu_emu.R 23import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 24import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
24import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting 25import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
@@ -29,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
29import org.yuzu.yuzu_emu.features.settings.model.StringSetting 30import org.yuzu.yuzu_emu.features.settings.model.StringSetting
30import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 31import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
31import org.yuzu.yuzu_emu.utils.* 32import org.yuzu.yuzu_emu.utils.*
32import java.io.IOException
33 33
34class SettingsActivity : AppCompatActivity(), SettingsActivityView { 34class SettingsActivity : AppCompatActivity(), SettingsActivityView {
35 private val presenter = SettingsActivityPresenter(this) 35 private val presenter = SettingsActivityPresenter(this)
@@ -59,7 +59,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
59 setSupportActionBar(binding.toolbarSettings) 59 setSupportActionBar(binding.toolbarSettings)
60 supportActionBar!!.setDisplayHomeAsUpEnabled(true) 60 supportActionBar!!.setDisplayHomeAsUpEnabled(true)
61 61
62 if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { 62 if (InsetsHelper.getSystemGestureType(applicationContext) !=
63 InsetsHelper.GESTURE_NAVIGATION
64 ) {
63 binding.navigationBarShade.setBackgroundColor( 65 binding.navigationBarShade.setBackgroundColor(
64 ThemeHelper.getColorWithOpacity( 66 ThemeHelper.getColorWithOpacity(
65 MaterialColors.getColor( 67 MaterialColors.getColor(
@@ -75,7 +77,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
75 this, 77 this,
76 object : OnBackPressedCallback(true) { 78 object : OnBackPressedCallback(true) {
77 override fun handleOnBackPressed() = navigateBack() 79 override fun handleOnBackPressed() = navigateBack()
78 }) 80 }
81 )
79 82
80 setInsets() 83 setInsets()
81 } 84 }
@@ -148,11 +151,13 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
148 private fun areSystemAnimationsEnabled(): Boolean { 151 private fun areSystemAnimationsEnabled(): Boolean {
149 val duration = android.provider.Settings.Global.getFloat( 152 val duration = android.provider.Settings.Global.getFloat(
150 contentResolver, 153 contentResolver,
151 android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f 154 android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
155 1f
152 ) 156 )
153 val transition = android.provider.Settings.Global.getFloat( 157 val transition = android.provider.Settings.Global.getFloat(
154 contentResolver, 158 contentResolver,
155 android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f 159 android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
160 1f
156 ) 161 )
157 return duration != 0f && transition != 0f 162 return duration != 0f && transition != 0f
158 } 163 }
@@ -207,7 +212,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
207 get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? 212 get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
208 213
209 private fun setInsets() { 214 private fun setInsets() {
210 ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat -> 215 ViewCompat.setOnApplyWindowInsetsListener(
216 binding.frameContent
217 ) { view: View, windowInsets: WindowInsetsCompat ->
211 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 218 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
212 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 219 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
213 view.updatePadding( 220 view.updatePadding(
@@ -239,5 +246,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
239 settings.putExtra(ARG_GAME_ID, gameId) 246 settings.putExtra(ARG_GAME_ID, gameId)
240 context.startActivity(settings) 247 context.startActivity(settings)
241 } 248 }
249
250 fun launch(
251 context: Context,
252 launcher: ActivityResultLauncher<Intent>,
253 menuTag: String?,
254 gameId: String?
255 ) {
256 val settings = Intent(context, SettingsActivity::class.java)
257 settings.putExtra(ARG_MENU_TAG, menuTag)
258 settings.putExtra(ARG_GAME_ID, gameId)
259 launcher.launch(settings)
260 }
242 } 261 }
243} 262}
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
index 4361d95fb..93e677b21 100644
--- 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
@@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.features.settings.ui
6import android.content.Context 6import android.content.Context
7import android.os.Bundle 7import android.os.Bundle
8import android.text.TextUtils 8import android.text.TextUtils
9import java.io.File
9import org.yuzu.yuzu_emu.NativeLibrary 10import org.yuzu.yuzu_emu.NativeLibrary
10import org.yuzu.yuzu_emu.features.settings.model.Settings 11import org.yuzu.yuzu_emu.features.settings.model.Settings
11import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 12import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
12import org.yuzu.yuzu_emu.utils.DirectoryInitialization 13import org.yuzu.yuzu_emu.utils.DirectoryInitialization
13import org.yuzu.yuzu_emu.utils.Log 14import org.yuzu.yuzu_emu.utils.Log
14import java.io.File
15 15
16class SettingsActivityPresenter(private val activityView: SettingsActivityView) { 16class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
17 val settings: Settings get() = activityView.settings 17 val settings: Settings get() = activityView.settings
@@ -46,9 +46,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
46 46
47 private fun prepareDirectoriesIfNeeded() { 47 private fun prepareDirectoriesIfNeeded() {
48 val configFile = 48 val configFile =
49 File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") 49 File(
50 "${DirectoryInitialization.userDirectory}/config/" +
51 "${SettingsFile.FILE_NAME_CONFIG}.ini"
52 )
50 if (!configFile.exists()) { 53 if (!configFile.exists()) {
51 Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") 54 Log.error(
55 "${DirectoryInitialization.userDirectory}/config/" +
56 "${SettingsFile.FILE_NAME_CONFIG}.ini"
57 )
52 Log.error("yuzu config file could not be found!") 58 Log.error("yuzu config file could not be found!")
53 } 59 }
54 60
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
index 1eb4899fc..ce0b92c90 100644
--- 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
@@ -13,7 +13,6 @@ import android.view.ViewGroup
13import android.widget.TextView 13import android.widget.TextView
14import androidx.appcompat.app.AlertDialog 14import androidx.appcompat.app.AlertDialog
15import androidx.appcompat.app.AppCompatActivity 15import androidx.appcompat.app.AppCompatActivity
16import androidx.fragment.app.setFragmentResultListener
17import androidx.recyclerview.widget.RecyclerView 16import androidx.recyclerview.widget.RecyclerView
18import com.google.android.material.datepicker.MaterialDatePicker 17import com.google.android.material.datepicker.MaterialDatePicker
19import com.google.android.material.dialog.MaterialAlertDialogBuilder 18import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -139,7 +138,7 @@ class SettingsAdapter(
139 clickedItem = item 138 clickedItem = item
140 dialog = MaterialAlertDialogBuilder(context) 139 dialog = MaterialAlertDialogBuilder(context)
141 .setTitle(item.nameId) 140 .setTitle(item.nameId)
142 .setSingleChoiceItems(item.choicesId, item.selectValueIndex, this) 141 .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
143 .show() 142 .show()
144 } 143 }
145 144
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
index 867147950..70a74c4dd 100644
--- 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
@@ -50,7 +50,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
50 50
51 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 51 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
52 settingsAdapter = SettingsAdapter(this, requireActivity()) 52 settingsAdapter = SettingsAdapter(this, requireActivity())
53 val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL) 53 val dividerDecoration = MaterialDividerItemDecoration(
54 requireContext(),
55 LinearLayoutManager.VERTICAL
56 )
54 dividerDecoration.isLastItemDecorated = false 57 dividerDecoration.isLastItemDecorated = false
55 binding.listSettings.apply { 58 binding.listSettings.apply {
56 adapter = settingsAdapter 59 adapter = settingsAdapter
@@ -99,7 +102,9 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
99 } 102 }
100 103
101 private fun setInsets() { 104 private fun setInsets() {
102 ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat -> 105 ViewCompat.setOnApplyWindowInsetsListener(
106 binding.listSettings
107 ) { view: View, windowInsets: WindowInsetsCompat ->
103 val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 108 val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
104 view.updatePadding(bottom = insets.bottom) 109 view.updatePadding(bottom = insets.bottom)
105 windowInsets 110 windowInsets
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
index 1ceaa6fb4..59c1d9d54 100644
--- 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
@@ -7,7 +7,6 @@ import android.content.SharedPreferences
7import android.os.Build 7import android.os.Build
8import android.text.TextUtils 8import android.text.TextUtils
9import androidx.preference.PreferenceManager 9import androidx.preference.PreferenceManager
10import com.google.android.material.dialog.MaterialAlertDialogBuilder
11import org.yuzu.yuzu_emu.R 10import org.yuzu.yuzu_emu.R
12import org.yuzu.yuzu_emu.YuzuApplication 11import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting 12import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
@@ -43,7 +42,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
43 } 42 }
44 43
45 fun putSetting(setting: AbstractSetting) { 44 fun putSetting(setting: AbstractSetting) {
46 if (setting.section == null) { 45 if (setting.section == null || setting.key == null) {
47 return 46 return
48 } 47 }
49 48
@@ -166,6 +165,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
166 IntSetting.CPU_ACCURACY.defaultValue 165 IntSetting.CPU_ACCURACY.defaultValue
167 ) 166 )
168 ) 167 )
168 add(
169 SwitchSetting(
170 BooleanSetting.PICTURE_IN_PICTURE,
171 R.string.picture_in_picture,
172 R.string.picture_in_picture_description,
173 BooleanSetting.PICTURE_IN_PICTURE.key,
174 BooleanSetting.PICTURE_IN_PICTURE.defaultValue
175 )
176 )
169 } 177 }
170 } 178 }
171 179
@@ -227,7 +235,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
227 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { 235 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
228 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) 236 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
229 sl.apply { 237 sl.apply {
230
231 add( 238 add(
232 SingleChoiceSetting( 239 SingleChoiceSetting(
233 IntSetting.RENDERER_ACCURACY, 240 IntSetting.RENDERER_ACCURACY,
@@ -285,6 +292,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
285 ) 292 )
286 add( 293 add(
287 SingleChoiceSetting( 294 SingleChoiceSetting(
295 IntSetting.RENDERER_SCREEN_LAYOUT,
296 R.string.renderer_screen_layout,
297 0,
298 R.array.rendererScreenLayoutNames,
299 R.array.rendererScreenLayoutValues,
300 IntSetting.RENDERER_SCREEN_LAYOUT.key,
301 IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
302 )
303 )
304 add(
305 SingleChoiceSetting(
288 IntSetting.RENDERER_ASPECT_RATIO, 306 IntSetting.RENDERER_ASPECT_RATIO,
289 R.string.renderer_aspect_ratio, 307 R.string.renderer_aspect_ratio,
290 0, 308 0,
@@ -335,18 +353,31 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
335 353
336 private fun addAudioSettings(sl: ArrayList<SettingsItem>) { 354 private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
337 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) 355 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
338 sl.add( 356 sl.apply {
339 SliderSetting( 357 add(
340 IntSetting.AUDIO_VOLUME, 358 StringSingleChoiceSetting(
341 R.string.audio_volume, 359 StringSetting.AUDIO_OUTPUT_ENGINE,
342 R.string.audio_volume_description, 360 R.string.audio_output_engine,
343 0, 361 0,
344 100, 362 settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
345 "%", 363 settingsActivity.resources.getStringArray(R.array.outputEngineValues),
346 IntSetting.AUDIO_VOLUME.key, 364 StringSetting.AUDIO_OUTPUT_ENGINE.key,
347 IntSetting.AUDIO_VOLUME.defaultValue 365 StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
348 ) 366 )
349 ) 367 )
368 add(
369 SliderSetting(
370 IntSetting.AUDIO_VOLUME,
371 R.string.audio_volume,
372 R.string.audio_volume_description,
373 0,
374 100,
375 "%",
376 IntSetting.AUDIO_VOLUME.key,
377 IntSetting.AUDIO_VOLUME.defaultValue
378 )
379 )
380 }
350 } 381 }
351 382
352 private fun addThemeSettings(sl: ArrayList<SettingsItem>) { 383 private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
@@ -449,6 +480,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
449 private fun addDebugSettings(sl: ArrayList<SettingsItem>) { 480 private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
450 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) 481 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
451 sl.apply { 482 sl.apply {
483 add(HeaderSetting(R.string.gpu))
452 add( 484 add(
453 SingleChoiceSetting( 485 SingleChoiceSetting(
454 IntSetting.RENDERER_BACKEND, 486 IntSetting.RENDERER_BACKEND,
@@ -469,6 +501,39 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
469 IntSetting.RENDERER_DEBUG.defaultValue 501 IntSetting.RENDERER_DEBUG.defaultValue
470 ) 502 )
471 ) 503 )
504
505 add(HeaderSetting(R.string.cpu))
506 add(
507 SwitchSetting(
508 BooleanSetting.CPU_DEBUG_MODE,
509 R.string.cpu_debug_mode,
510 R.string.cpu_debug_mode_description,
511 BooleanSetting.CPU_DEBUG_MODE.key,
512 BooleanSetting.CPU_DEBUG_MODE.defaultValue
513 )
514 )
515
516 val fastmem = object : AbstractBooleanSetting {
517 override var boolean: Boolean
518 get() =
519 BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
520 set(value) {
521 BooleanSetting.FASTMEM.boolean = value
522 BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
523 }
524 override val key: String? = null
525 override val section: String = Settings.SECTION_CPU
526 override val isRuntimeEditable: Boolean = false
527 override val valueAsString: String = ""
528 override val defaultValue: Any = true
529 }
530 add(
531 SwitchSetting(
532 fastmem,
533 R.string.fastmem,
534 0
535 )
536 )
472 } 537 }
473 } 538 }
474} 539}
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
index 04c045e77..7955532ee 100644
--- 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
@@ -4,15 +4,15 @@
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder 4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5 5
6import android.view.View 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 7import java.time.Instant
12import java.time.ZoneId 8import java.time.ZoneId
13import java.time.ZonedDateTime 9import java.time.ZonedDateTime
14import java.time.format.DateTimeFormatter 10import java.time.format.DateTimeFormatter
15import java.time.format.FormatStyle 11import java.time.format.FormatStyle
12import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
13import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
14import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
15import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
16 16
17class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : 17class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
18 SettingViewHolder(binding.root, adapter) { 18 SettingViewHolder(binding.root, adapter) {
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
index de764a27f..e4e321bd3 100644
--- 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
@@ -26,6 +26,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
26 for (i in values.indices) { 26 for (i in values.indices) {
27 if (values[i] == item.selectedValue) { 27 if (values[i] == item.selectedValue) {
28 binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] 28 binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i]
29 return
30 }
31 }
32 } else if (item is StringSingleChoiceSetting) {
33 for (i in item.values!!.indices) {
34 if (item.values[i] == item.selectedValue) {
35 binding.textSettingDescription.text = item.choices[i]
36 return
29 } 37 }
30 } 38 }
31 } else { 39 } else {
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
index b163bd6ca..54f531795 100644
--- 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
@@ -6,8 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
6import android.view.View 6import android.view.View
7import android.widget.CompoundButton 7import android.widget.CompoundButton
8import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding 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 9import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
10import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter 11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
12 12
13class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : 13class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
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
index e29bca11d..70a52df5d 100644
--- 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
@@ -3,6 +3,8 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.utils 4package org.yuzu.yuzu_emu.features.settings.utils
5 5
6import java.io.*
7import java.util.*
6import org.ini4j.Wini 8import org.ini4j.Wini
7import org.yuzu.yuzu_emu.NativeLibrary 9import org.yuzu.yuzu_emu.NativeLibrary
8import org.yuzu.yuzu_emu.R 10import org.yuzu.yuzu_emu.R
@@ -13,8 +15,6 @@ import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
13import org.yuzu.yuzu_emu.utils.BiMap 15import org.yuzu.yuzu_emu.utils.BiMap
14import org.yuzu.yuzu_emu.utils.DirectoryInitialization 16import org.yuzu.yuzu_emu.utils.DirectoryInitialization
15import org.yuzu.yuzu_emu.utils.Log 17import org.yuzu.yuzu_emu.utils.Log
16import java.io.*
17import java.util.*
18 18
19/** 19/**
20 * Contains static methods for interacting with .ini files in which settings are stored. 20 * Contains static methods for interacting with .ini files in which settings are stored.
@@ -137,9 +137,12 @@ object SettingsFile {
137 for (settingKey in sortedKeySet) { 137 for (settingKey in sortedKeySet) {
138 val setting = settings[settingKey] 138 val setting = settings[settingKey]
139 NativeLibrary.setUserSetting( 139 NativeLibrary.setUserSetting(
140 gameId, mapSectionNameFromIni( 140 gameId,
141 mapSectionNameFromIni(
141 section.name 142 section.name
142 ), setting!!.key, setting.valueAsString 143 ),
144 setting!!.key,
145 setting.valueAsString
143 ) 146 )
144 } 147 }
145 } 148 }
@@ -148,13 +151,17 @@ object SettingsFile {
148 private fun mapSectionNameFromIni(generalSectionName: String): String? { 151 private fun mapSectionNameFromIni(generalSectionName: String): String? {
149 return if (sectionsMap.getForward(generalSectionName) != null) { 152 return if (sectionsMap.getForward(generalSectionName) != null) {
150 sectionsMap.getForward(generalSectionName) 153 sectionsMap.getForward(generalSectionName)
151 } else generalSectionName 154 } else {
155 generalSectionName
156 }
152 } 157 }
153 158
154 private fun mapSectionNameToIni(generalSectionName: String): String { 159 private fun mapSectionNameToIni(generalSectionName: String): String {
155 return if (sectionsMap.getBackward(generalSectionName) != null) { 160 return if (sectionsMap.getBackward(generalSectionName) != null) {
156 sectionsMap.getBackward(generalSectionName).toString() 161 sectionsMap.getBackward(generalSectionName).toString()
157 } else generalSectionName 162 } else {
163 generalSectionName
164 }
158 } 165 }
159 166
160 fun getSettingsFile(fileName: String): File { 167 fun getSettingsFile(fileName: String): File {
@@ -237,5 +244,21 @@ object SettingsFile {
237 val setting = settings[key] 244 val setting = settings[key]
238 parser.put(header, setting!!.key, setting.valueAsString) 245 parser.put(header, setting!!.key, setting.valueAsString)
239 } 246 }
247
248 BooleanSetting.values().forEach {
249 if (!keySet.contains(it.key)) {
250 parser.put(header, it.key, it.valueAsString)
251 }
252 }
253 IntSetting.values().forEach {
254 if (!keySet.contains(it.key)) {
255 parser.put(header, it.key, it.valueAsString)
256 }
257 }
258 StringSetting.values().forEach {
259 if (!keySet.contains(it.key)) {
260 parser.put(header, it.key, it.valueAsString)
261 }
262 }
240 } 263 }
241} 264}
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
index c92e2755c..2ff827c6b 100644
--- 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
@@ -66,7 +66,11 @@ class AboutFragment : Fragment() {
66 true 66 true
67 } 67 }
68 68
69 binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) } 69 binding.buttonContributors.setOnClickListener {
70 openLink(
71 getString(R.string.contributors_link)
72 )
73 }
70 binding.buttonLicenses.setOnClickListener { 74 binding.buttonLicenses.setOnClickListener {
71 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) 75 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
72 binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) 76 binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
@@ -101,7 +105,9 @@ class AboutFragment : Fragment() {
101 } 105 }
102 106
103 private fun setInsets() = 107 private fun setInsets() =
104 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> 108 ViewCompat.setOnApplyWindowInsetsListener(
109 binding.root
110 ) { _: View, windowInsets: WindowInsetsCompat ->
105 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 111 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
106 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 112 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
107 113
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
index d8bbc1ce4..dbc16da4a 100644
--- 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
@@ -49,7 +49,11 @@ class EarlyAccessFragment : Fragment() {
49 parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() 49 parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack()
50 } 50 }
51 51
52 binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) } 52 binding.getEarlyAccessButton.setOnClickListener {
53 openLink(
54 getString(R.string.play_store_link)
55 )
56 }
53 57
54 setInsets() 58 setInsets()
55 } 59 }
@@ -60,7 +64,9 @@ class EarlyAccessFragment : Fragment() {
60 } 64 }
61 65
62 private fun setInsets() = 66 private fun setInsets() =
63 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> 67 ViewCompat.setOnApplyWindowInsetsListener(
68 binding.root
69 ) { _: View, windowInsets: WindowInsetsCompat ->
64 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 70 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
65 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 71 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
66 72
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
index 9523381cd..4643418c1 100644
--- 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
@@ -7,30 +7,39 @@ import android.annotation.SuppressLint
7import android.app.AlertDialog 7import android.app.AlertDialog
8import android.content.Context 8import android.content.Context
9import android.content.DialogInterface 9import android.content.DialogInterface
10import android.content.Intent
10import android.content.SharedPreferences 11import android.content.SharedPreferences
11import android.content.pm.ActivityInfo 12import android.content.pm.ActivityInfo
12import android.content.res.Resources 13import android.content.res.Configuration
13import android.graphics.Color 14import android.graphics.Color
14import android.os.Bundle 15import android.os.Bundle
15import android.os.Handler 16import android.os.Handler
16import android.os.Looper 17import android.os.Looper
17import android.util.Rational 18import android.util.Rational
18import android.util.TypedValue
19import android.view.* 19import android.view.*
20import android.widget.TextView 20import android.widget.TextView
21import androidx.activity.OnBackPressedCallback 21import androidx.activity.OnBackPressedCallback
22import androidx.activity.result.ActivityResultLauncher
23import androidx.activity.result.contract.ActivityResultContracts
22import androidx.appcompat.widget.PopupMenu 24import androidx.appcompat.widget.PopupMenu
23import androidx.core.content.res.ResourcesCompat 25import androidx.core.content.res.ResourcesCompat
24import androidx.core.graphics.Insets 26import androidx.core.graphics.Insets
25import androidx.core.view.ViewCompat 27import androidx.core.view.ViewCompat
26import androidx.core.view.WindowInsetsCompat 28import androidx.core.view.WindowInsetsCompat
27import androidx.core.view.updatePadding 29import androidx.core.view.isVisible
28import androidx.fragment.app.Fragment 30import androidx.fragment.app.Fragment
31import androidx.lifecycle.Lifecycle
32import androidx.lifecycle.lifecycleScope
33import androidx.lifecycle.repeatOnLifecycle
34import androidx.navigation.fragment.navArgs
29import androidx.preference.PreferenceManager 35import androidx.preference.PreferenceManager
30import androidx.window.layout.FoldingFeature 36import androidx.window.layout.FoldingFeature
37import androidx.window.layout.WindowInfoTracker
31import androidx.window.layout.WindowLayoutInfo 38import androidx.window.layout.WindowLayoutInfo
32import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
33import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.launch
34import org.yuzu.yuzu_emu.NativeLibrary 43import org.yuzu.yuzu_emu.NativeLibrary
35import org.yuzu.yuzu_emu.R 44import org.yuzu.yuzu_emu.R
36import org.yuzu.yuzu_emu.YuzuApplication 45import org.yuzu.yuzu_emu.YuzuApplication
@@ -41,9 +50,8 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
41import org.yuzu.yuzu_emu.features.settings.model.Settings 50import org.yuzu.yuzu_emu.features.settings.model.Settings
42import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity 51import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
43import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 52import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
44import org.yuzu.yuzu_emu.model.Game 53import org.yuzu.yuzu_emu.overlay.InputOverlay
45import org.yuzu.yuzu_emu.utils.* 54import org.yuzu.yuzu_emu.utils.*
46import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
47 55
48class EmulationFragment : Fragment(), SurfaceHolder.Callback { 56class EmulationFragment : Fragment(), SurfaceHolder.Callback {
49 private lateinit var preferences: SharedPreferences 57 private lateinit var preferences: SharedPreferences
@@ -54,13 +62,30 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
54 private var _binding: FragmentEmulationBinding? = null 62 private var _binding: FragmentEmulationBinding? = null
55 private val binding get() = _binding!! 63 private val binding get() = _binding!!
56 64
57 private lateinit var game: Game 65 val args by navArgs<EmulationFragmentArgs>()
66
67 private var isInFoldableLayout = false
68
69 private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
58 70
59 override fun onAttach(context: Context) { 71 override fun onAttach(context: Context) {
60 super.onAttach(context) 72 super.onAttach(context)
61 if (context is EmulationActivity) { 73 if (context is EmulationActivity) {
62 emulationActivity = context 74 emulationActivity = context
63 NativeLibrary.setEmulationActivity(context) 75 NativeLibrary.setEmulationActivity(context)
76
77 lifecycleScope.launch(Dispatchers.Main) {
78 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
79 WindowInfoTracker.getOrCreate(context)
80 .windowLayoutInfo(context)
81 .collect { updateFoldableLayout(context, it) }
82 }
83 }
84
85 onReturnFromSettings = context.activityResultRegistry.register(
86 "SettingsResult",
87 ActivityResultContracts.StartActivityForResult()
88 ) { updateScreenLayout() }
64 } else { 89 } else {
65 throw IllegalStateException("EmulationFragment must have EmulationActivity parent") 90 throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
66 } 91 }
@@ -75,8 +100,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
75 // So this fragment doesn't restart on configuration changes; i.e. rotation. 100 // So this fragment doesn't restart on configuration changes; i.e. rotation.
76 retainInstance = true 101 retainInstance = true
77 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 102 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
78 game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!! 103 emulationState = EmulationState(args.game.path)
79 emulationState = EmulationState(game.path)
80 } 104 }
81 105
82 /** 106 /**
@@ -100,7 +124,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
100 updateShowFpsOverlay() 124 updateShowFpsOverlay()
101 125
102 binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = 126 binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
103 game.title 127 args.game.title
104 binding.inGameMenu.setNavigationItemSelectedListener { 128 binding.inGameMenu.setNavigationItemSelectedListener {
105 when (it.itemId) { 129 when (it.itemId) {
106 R.id.menu_pause_emulation -> { 130 R.id.menu_pause_emulation -> {
@@ -125,7 +149,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
125 } 149 }
126 150
127 R.id.menu_settings -> { 151 R.id.menu_settings -> {
128 SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") 152 SettingsActivity.launch(
153 requireContext(),
154 onReturnFromSettings,
155 SettingsFile.FILE_NAME_CONFIG,
156 ""
157 )
129 true 158 true
130 } 159 }
131 160
@@ -150,9 +179,48 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
150 requireActivity(), 179 requireActivity(),
151 object : OnBackPressedCallback(true) { 180 object : OnBackPressedCallback(true) {
152 override fun handleOnBackPressed() { 181 override fun handleOnBackPressed() {
153 if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open() 182 if (binding.drawerLayout.isOpen) {
183 binding.drawerLayout.close()
184 } else {
185 binding.drawerLayout.open()
186 }
187 }
188 }
189 )
190
191 viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
192 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
193 WindowInfoTracker.getOrCreate(requireContext())
194 .windowLayoutInfo(requireActivity())
195 .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
196 }
197 }
198 }
199
200 override fun onConfigurationChanged(newConfig: Configuration) {
201 super.onConfigurationChanged(newConfig)
202 if (emulationActivity?.isInPictureInPictureMode == true) {
203 if (binding.drawerLayout.isOpen) {
204 binding.drawerLayout.close()
205 }
206 if (EmulationMenuSettings.showOverlay) {
207 binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
208 }
209 } else {
210 if (EmulationMenuSettings.showOverlay) {
211 binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
212 }
213 if (!isInFoldableLayout) {
214 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
215 binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT
216 } else {
217 binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE
154 } 218 }
155 }) 219 }
220 if (!binding.surfaceInputOverlay.isInEditMode) {
221 refreshInputOverlay()
222 }
223 }
156 } 224 }
157 225
158 override fun onResume() { 226 override fun onResume() {
@@ -161,16 +229,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
161 DirectoryInitialization.start(requireContext()) 229 DirectoryInitialization.start(requireContext())
162 } 230 }
163 231
164 binding.surfaceEmulation.setAspectRatio( 232 updateScreenLayout()
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 233
175 emulationState.run(emulationActivity!!.isActivityRecreated) 234 emulationState.run(emulationActivity!!.isActivityRecreated)
176 } 235 }
@@ -231,31 +290,72 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
231 } 290 }
232 } 291 }
233 292
234 private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt() 293 @SuppressLint("SourceLockedOrientationActivity")
235 294 private fun updateOrientation() {
236 fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { 295 emulationActivity?.let {
237 val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { 296 it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
238 if (it.isSeparating) { 297 Settings.LayoutOption_MobileLandscape ->
239 emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED 298 ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
240 if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { 299 Settings.LayoutOption_MobilePortrait ->
241 binding.surfaceEmulation.layoutParams.height = it.bounds.top 300 ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
242 binding.inGameMenu.layoutParams.height = it.bounds.bottom 301 Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
243 binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx 302 else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
244 binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx)
245 }
246 } 303 }
247 it.isSeparating 304 }
248 } ?: false 305 }
306
307 private fun updateScreenLayout() {
308 binding.surfaceEmulation.setAspectRatio(
309 when (IntSetting.RENDERER_ASPECT_RATIO.int) {
310 0 -> Rational(16, 9)
311 1 -> Rational(4, 3)
312 2 -> Rational(21, 9)
313 3 -> Rational(16, 10)
314 4 -> null // Stretch
315 else -> Rational(16, 9)
316 }
317 )
318 emulationActivity?.buildPictureInPictureParams()
319 updateOrientation()
320 }
321
322 private fun updateFoldableLayout(
323 emulationActivity: EmulationActivity,
324 newLayoutInfo: WindowLayoutInfo
325 ) {
326 val isFolding =
327 (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
328 if (it.isSeparating) {
329 emulationActivity.requestedOrientation =
330 ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
331 if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
332 // Restrict emulation and overlays to the top of the screen
333 binding.emulationContainer.layoutParams.height = it.bounds.top
334 binding.overlayContainer.layoutParams.height = it.bounds.top
335 // Restrict input and menu drawer to the bottom of the screen
336 binding.inputContainer.layoutParams.height = it.bounds.bottom
337 binding.inGameMenu.layoutParams.height = it.bounds.bottom
338
339 isInFoldableLayout = true
340 binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE
341 refreshInputOverlay()
342 }
343 }
344 it.isSeparating
345 } ?: false
249 if (!isFolding) { 346 if (!isFolding) {
250 binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT 347 binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
251 binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT 348 binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
252 binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT 349 binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
253 binding.overlayContainer.updatePadding(0, 0, 0, 0) 350 binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
254 emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE 351 isInFoldableLayout = false
352 updateOrientation()
353 onConfigurationChanged(resources.configuration)
255 } 354 }
256 binding.surfaceInputOverlay.requestLayout() 355 binding.emulationContainer.requestLayout()
257 binding.inGameMenu.requestLayout() 356 binding.inputContainer.requestLayout()
258 binding.overlayContainer.requestLayout() 357 binding.overlayContainer.requestLayout()
358 binding.inGameMenu.requestLayout()
259 } 359 }
260 360
261 override fun surfaceCreated(holder: SurfaceHolder) { 361 override fun surfaceCreated(holder: SurfaceHolder) {
@@ -385,7 +485,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
385 popup.show() 485 popup.show()
386 } 486 }
387 487
488 @SuppressLint("SourceLockedOrientationActivity")
388 private fun startConfiguringControls() { 489 private fun startConfiguringControls() {
490 // Lock the current orientation to prevent editing inconsistencies
491 if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
492 emulationActivity?.let {
493 it.requestedOrientation =
494 if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
495 ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
496 } else {
497 ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
498 }
499 }
500 }
389 binding.doneControlConfig.visibility = View.VISIBLE 501 binding.doneControlConfig.visibility = View.VISIBLE
390 binding.surfaceInputOverlay.setIsInEditMode(true) 502 binding.surfaceInputOverlay.setIsInEditMode(true)
391 } 503 }
@@ -393,6 +505,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
393 private fun stopConfiguringControls() { 505 private fun stopConfiguringControls() {
394 binding.doneControlConfig.visibility = View.GONE 506 binding.doneControlConfig.visibility = View.GONE
395 binding.surfaceInputOverlay.setIsInEditMode(false) 507 binding.surfaceInputOverlay.setIsInEditMode(false)
508 // Unlock the orientation if it was locked for editing
509 if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
510 emulationActivity?.let {
511 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
512 }
513 }
396 } 514 }
397 515
398 @SuppressLint("SetTextI18n") 516 @SuppressLint("SetTextI18n")
@@ -402,18 +520,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
402 inputScaleSlider.apply { 520 inputScaleSlider.apply {
403 valueTo = 150F 521 valueTo = 150F
404 value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() 522 value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
405 addOnChangeListener(Slider.OnChangeListener { _, value, _ -> 523 addOnChangeListener(
406 inputScaleValue.text = "${value.toInt()}%" 524 Slider.OnChangeListener { _, value, _ ->
407 setControlScale(value.toInt()) 525 inputScaleValue.text = "${value.toInt()}%"
408 }) 526 setControlScale(value.toInt())
527 }
528 )
409 } 529 }
410 inputOpacitySlider.apply { 530 inputOpacitySlider.apply {
411 valueTo = 100F 531 valueTo = 100F
412 value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() 532 value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
413 addOnChangeListener(Slider.OnChangeListener { _, value, _ -> 533 addOnChangeListener(
414 inputOpacityValue.text = "${value.toInt()}%" 534 Slider.OnChangeListener { _, value, _ ->
415 setControlOpacity(value.toInt()) 535 inputOpacityValue.text = "${value.toInt()}%"
416 }) 536 setControlOpacity(value.toInt())
537 }
538 )
417 } 539 }
418 inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" 540 inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
419 inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" 541 inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
@@ -445,7 +567,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
445 } 567 }
446 568
447 private fun setInsets() { 569 private fun setInsets() {
448 ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat -> 570 ViewCompat.setOnApplyWindowInsetsListener(
571 binding.inGameMenu
572 ) { v: View, windowInsets: WindowInsetsCompat ->
449 val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 573 val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
450 var left = 0 574 var left = 0
451 var right = 0 575 var right = 0
@@ -565,8 +689,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
565 state = State.PAUSED 689 state = State.PAUSED
566 } 690 }
567 691
568 State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.") 692 State.PAUSED -> Log.warning(
569 else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.") 693 "[EmulationFragment] Surface cleared while emulation paused."
694 )
695 else -> Log.warning(
696 "[EmulationFragment] Surface cleared while emulation stopped."
697 )
570 } 698 }
571 } 699 }
572 } 700 }
@@ -601,13 +729,5 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
601 729
602 companion object { 730 companion object {
603 private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) 731 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 } 732 }
613} 733}
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
index 536163eb6..5a36ffad4 100644
--- 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
@@ -68,77 +68,109 @@ class HomeSettingsFragment : Fragment() {
68 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 68 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
69 mainActivity = requireActivity() as MainActivity 69 mainActivity = requireActivity() as MainActivity
70 70
71 val optionsList: MutableList<HomeSetting> = mutableListOf( 71 val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
72 HomeSetting( 72 add(
73 R.string.advanced_settings, 73 HomeSetting(
74 R.string.settings_description, 74 R.string.advanced_settings,
75 R.drawable.ic_settings 75 R.string.settings_description,
76 ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }, 76 R.drawable.ic_settings
77 HomeSetting( 77 ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
78 R.string.open_user_folder, 78 )
79 R.string.open_user_folder_description, 79 add(
80 R.drawable.ic_folder_open 80 HomeSetting(
81 ) { openFileManager() }, 81 R.string.open_user_folder,
82 HomeSetting( 82 R.string.open_user_folder_description,
83 R.string.preferences_theme, 83 R.drawable.ic_folder_open
84 R.string.theme_and_color_description, 84 ) { openFileManager() }
85 R.drawable.ic_palette 85 )
86 ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }, 86 add(
87 HomeSetting( 87 HomeSetting(
88 R.string.install_gpu_driver, 88 R.string.preferences_theme,
89 R.string.install_gpu_driver_description, 89 R.string.theme_and_color_description,
90 R.drawable.ic_exit 90 R.drawable.ic_palette
91 ) { driverInstaller() }, 91 ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
92 HomeSetting( 92 )
93 R.string.install_amiibo_keys, 93
94 R.string.install_amiibo_keys_description, 94 if (GpuDriverHelper.supportsCustomDriverLoading()) {
95 R.drawable.ic_nfc 95 add(
96 ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }, 96 HomeSetting(
97 HomeSetting( 97 R.string.install_gpu_driver,
98 R.string.install_game_content, 98 R.string.install_gpu_driver_description,
99 R.string.install_game_content_description, 99 R.drawable.ic_exit
100 R.drawable.ic_system_update_alt 100 ) { driverInstaller() }
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 ) 101 )
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 } 102 }
141 ) 103
104 add(
105 HomeSetting(
106 R.string.install_amiibo_keys,
107 R.string.install_amiibo_keys_description,
108 R.drawable.ic_nfc
109 ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
110 )
111 add(
112 HomeSetting(
113 R.string.install_game_content,
114 R.string.install_game_content_description,
115 R.drawable.ic_system_update_alt
116 ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
117 )
118 add(
119 HomeSetting(
120 R.string.select_games_folder,
121 R.string.select_games_folder_description,
122 R.drawable.ic_add
123 ) {
124 mainActivity.getGamesDirectory.launch(
125 Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
126 )
127 }
128 )
129 add(
130 HomeSetting(
131 R.string.manage_save_data,
132 R.string.import_export_saves_description,
133 R.drawable.ic_save
134 ) {
135 ImportExportSavesFragment().show(
136 parentFragmentManager,
137 ImportExportSavesFragment.TAG
138 )
139 }
140 )
141 add(
142 HomeSetting(
143 R.string.install_prod_keys,
144 R.string.install_prod_keys_description,
145 R.drawable.ic_unlock
146 ) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
147 )
148 add(
149 HomeSetting(
150 R.string.install_firmware,
151 R.string.install_firmware_description,
152 R.drawable.ic_firmware
153 ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
154 )
155 add(
156 HomeSetting(
157 R.string.share_log,
158 R.string.share_log_description,
159 R.drawable.ic_log
160 ) { shareLog() }
161 )
162 add(
163 HomeSetting(
164 R.string.about,
165 R.string.about_description,
166 R.drawable.ic_info_outline
167 ) {
168 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
169 parentFragmentManager.primaryNavigationFragment?.findNavController()
170 ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
171 }
172 )
173 }
142 174
143 if (!BuildConfig.PREMIUM) { 175 if (!BuildConfig.PREMIUM) {
144 optionsList.add( 176 optionsList.add(
@@ -225,7 +257,11 @@ class HomeSettingsFragment : Fragment() {
225 val intent = Intent(action) 257 val intent = Intent(action)
226 intent.addCategory(Intent.CATEGORY_DEFAULT) 258 intent.addCategory(Intent.CATEGORY_DEFAULT)
227 intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) 259 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) 260 intent.addFlags(
261 Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
262 Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
263 Intent.FLAG_GRANT_WRITE_URI_PERMISSION
264 )
229 return intent 265 return intent
230 } 266 }
231 267
@@ -307,7 +343,9 @@ class HomeSettingsFragment : Fragment() {
307 } 343 }
308 344
309 private fun setInsets() = 345 private fun setInsets() =
310 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> 346 ViewCompat.setOnApplyWindowInsetsListener(
347 binding.root
348 ) { view: View, windowInsets: WindowInsetsCompat ->
311 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 349 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
312 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 350 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
313 val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) 351 val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
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
index 36e63bb9e..e1495ee8c 100644
--- 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
@@ -15,6 +15,14 @@ import androidx.appcompat.app.AppCompatActivity
15import androidx.documentfile.provider.DocumentFile 15import androidx.documentfile.provider.DocumentFile
16import androidx.fragment.app.DialogFragment 16import androidx.fragment.app.DialogFragment
17import com.google.android.material.dialog.MaterialAlertDialogBuilder 17import com.google.android.material.dialog.MaterialAlertDialogBuilder
18import java.io.BufferedOutputStream
19import java.io.File
20import java.io.FileOutputStream
21import java.io.FilenameFilter
22import java.time.LocalDateTime
23import java.time.format.DateTimeFormatter
24import java.util.zip.ZipEntry
25import java.util.zip.ZipOutputStream
18import kotlinx.coroutines.CoroutineScope 26import kotlinx.coroutines.CoroutineScope
19import kotlinx.coroutines.Dispatchers 27import kotlinx.coroutines.Dispatchers
20import kotlinx.coroutines.launch 28import kotlinx.coroutines.launch
@@ -24,14 +32,6 @@ import org.yuzu.yuzu_emu.YuzuApplication
24import org.yuzu.yuzu_emu.features.DocumentProvider 32import org.yuzu.yuzu_emu.features.DocumentProvider
25import org.yuzu.yuzu_emu.getPublicFilesDir 33import org.yuzu.yuzu_emu.getPublicFilesDir
26import org.yuzu.yuzu_emu.utils.FileUtil 34import 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 35
36class ImportExportSavesFragment : DialogFragment() { 36class ImportExportSavesFragment : DialogFragment() {
37 private val context = YuzuApplication.appContext 37 private val context = YuzuApplication.appContext
@@ -98,7 +98,7 @@ class ImportExportSavesFragment : DialogFragment() {
98 val outputZipFile = File( 98 val outputZipFile = File(
99 tempFolder, 99 tempFolder,
100 "yuzu saves - ${ 100 "yuzu saves - ${
101 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) 101 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
102 }.zip" 102 }.zip"
103 ) 103 )
104 outputZipFile.createNewFile() 104 outputZipFile.createNewFile()
@@ -106,12 +106,14 @@ class ImportExportSavesFragment : DialogFragment() {
106 saveFolder.walkTopDown().forEach { file -> 106 saveFolder.walkTopDown().forEach { file ->
107 val zipFileName = 107 val zipFileName =
108 file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") 108 file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
109 if (zipFileName == "") 109 if (zipFileName == "") {
110 return@forEach 110 return@forEach
111 }
111 val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") 112 val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
112 zos.putNextEntry(entry) 113 zos.putNextEntry(entry)
113 if (file.isFile) 114 if (file.isFile) {
114 file.inputStream().use { fis -> fis.copyTo(zos) } 115 file.inputStream().use { fis -> fis.copyTo(zos) }
116 }
115 } 117 }
116 } 118 }
117 lastZipCreated = outputZipFile 119 lastZipCreated = outputZipFile
@@ -137,7 +139,8 @@ class ImportExportSavesFragment : DialogFragment() {
137 139
138 withContext(Dispatchers.Main) { 140 withContext(Dispatchers.Main) {
139 val file = DocumentFile.fromSingleUri( 141 val file = DocumentFile.fromSingleUri(
140 context, DocumentsContract.buildDocumentUri( 142 context,
143 DocumentsContract.buildDocumentUri(
141 DocumentProvider.AUTHORITY, 144 DocumentProvider.AUTHORITY,
142 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" 145 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
143 ) 146 )
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
index c7880d8cc..739b26f99 100644
--- 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
@@ -14,7 +14,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
15import org.yuzu.yuzu_emu.model.TaskViewModel 15import org.yuzu.yuzu_emu.model.TaskViewModel
16 16
17
18class IndeterminateProgressDialogFragment : DialogFragment() { 17class IndeterminateProgressDialogFragment : DialogFragment() {
19 private val taskViewModel: TaskViewModel by activityViewModels() 18 private val taskViewModel: TaskViewModel by activityViewModels()
20 19
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
index 59141e823..b6e9129f7 100644
--- 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
@@ -113,7 +113,9 @@ class LicensesFragment : Fragment() {
113 } 113 }
114 114
115 private fun setInsets() = 115 private fun setInsets() =
116 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> 116 ViewCompat.setOnApplyWindowInsetsListener(
117 binding.root
118 ) { _: View, windowInsets: WindowInsetsCompat ->
117 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 119 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
118 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 120 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
119 121
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
index adbe3696b..dd6c895fd 100644
--- 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
@@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels
20import androidx.preference.PreferenceManager 20import androidx.preference.PreferenceManager
21import info.debatty.java.stringsimilarity.Jaccard 21import info.debatty.java.stringsimilarity.Jaccard
22import info.debatty.java.stringsimilarity.JaroWinkler 22import info.debatty.java.stringsimilarity.JaroWinkler
23import java.util.Locale
23import org.yuzu.yuzu_emu.R 24import org.yuzu.yuzu_emu.R
24import org.yuzu.yuzu_emu.YuzuApplication 25import org.yuzu.yuzu_emu.YuzuApplication
25import org.yuzu.yuzu_emu.adapters.GameAdapter 26import org.yuzu.yuzu_emu.adapters.GameAdapter
@@ -29,8 +30,6 @@ import org.yuzu.yuzu_emu.model.Game
29import org.yuzu.yuzu_emu.model.GamesViewModel 30import org.yuzu.yuzu_emu.model.GamesViewModel
30import org.yuzu.yuzu_emu.model.HomeViewModel 31import org.yuzu.yuzu_emu.model.HomeViewModel
31import org.yuzu.yuzu_emu.utils.FileUtil 32import org.yuzu.yuzu_emu.utils.FileUtil
32import org.yuzu.yuzu_emu.utils.Log
33import java.util.Locale
34 33
35class SearchFragment : Fragment() { 34class SearchFragment : Fragment() {
36 private var _binding: FragmentSearchBinding? = null 35 private var _binding: FragmentSearchBinding? = null
@@ -130,15 +129,15 @@ class SearchFragment : Fragment() {
130 R.id.chip_homebrew -> baseList.filter { it.isHomebrew } 129 R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
131 130
132 R.id.chip_retail -> baseList.filter { 131 R.id.chip_retail -> baseList.filter {
133 FileUtil.hasExtension(it.path, "xci") 132 FileUtil.hasExtension(it.path, "xci") ||
134 || FileUtil.hasExtension(it.path, "nsp") 133 FileUtil.hasExtension(it.path, "nsp")
135 } 134 }
136 135
137 else -> baseList 136 else -> baseList
138 } 137 }
139 138
140 if (binding.searchText.text.toString().isEmpty() 139 if (binding.searchText.text.toString().isEmpty() &&
141 && binding.chipGroup.checkedChipId != View.NO_ID 140 binding.chipGroup.checkedChipId != View.NO_ID
142 ) { 141 ) {
143 gamesViewModel.setSearchedGames(filteredList) 142 gamesViewModel.setSearchedGames(filteredList)
144 return 143 return
@@ -173,14 +172,16 @@ class SearchFragment : Fragment() {
173 private fun focusSearch() { 172 private fun focusSearch() {
174 if (_binding != null) { 173 if (_binding != null) {
175 binding.searchText.requestFocus() 174 binding.searchText.requestFocus()
176 val imm = 175 val imm = requireActivity()
177 requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? 176 .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
178 imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) 177 imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
179 } 178 }
180 } 179 }
181 180
182 private fun setInsets() = 181 private fun setInsets() =
183 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> 182 ViewCompat.setOnApplyWindowInsetsListener(
183 binding.root
184 ) { view: View, windowInsets: WindowInsetsCompat ->
184 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 185 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
185 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 186 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
186 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) 187 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
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
index 258773380..6c4ddaf6b 100644
--- 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
@@ -25,6 +25,7 @@ import androidx.navigation.findNavController
25import androidx.preference.PreferenceManager 25import androidx.preference.PreferenceManager
26import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback 26import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
27import com.google.android.material.transition.MaterialFadeThrough 27import com.google.android.material.transition.MaterialFadeThrough
28import java.io.File
28import org.yuzu.yuzu_emu.R 29import org.yuzu.yuzu_emu.R
29import org.yuzu.yuzu_emu.YuzuApplication 30import org.yuzu.yuzu_emu.YuzuApplication
30import org.yuzu.yuzu_emu.adapters.SetupAdapter 31import org.yuzu.yuzu_emu.adapters.SetupAdapter
@@ -35,7 +36,6 @@ import org.yuzu.yuzu_emu.model.SetupPage
35import org.yuzu.yuzu_emu.ui.main.MainActivity 36import org.yuzu.yuzu_emu.ui.main.MainActivity
36import org.yuzu.yuzu_emu.utils.DirectoryInitialization 37import org.yuzu.yuzu_emu.utils.DirectoryInitialization
37import org.yuzu.yuzu_emu.utils.GameHelper 38import org.yuzu.yuzu_emu.utils.GameHelper
38import java.io.File
39 39
40class SetupFragment : Fragment() { 40class SetupFragment : Fragment() {
41 private var _binding: FragmentSetupBinding? = null 41 private var _binding: FragmentSetupBinding? = null
@@ -82,7 +82,8 @@ class SetupFragment : Fragment() {
82 requireActivity().finish() 82 requireActivity().finish()
83 } 83 }
84 } 84 }
85 }) 85 }
86 )
86 87
87 requireActivity().window.navigationBarColor = 88 requireActivity().window.navigationBarColor =
88 ContextCompat.getColor(requireContext(), android.R.color.transparent) 89 ContextCompat.getColor(requireContext(), android.R.color.transparent)
@@ -148,14 +149,20 @@ class SetupFragment : Fragment() {
148 R.drawable.ic_add, 149 R.drawable.ic_add,
149 true, 150 true,
150 R.string.add_games, 151 R.string.add_games,
151 { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, 152 {
153 mainActivity.getGamesDirectory.launch(
154 Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
155 )
156 },
152 true, 157 true,
153 R.string.add_games_warning, 158 R.string.add_games_warning,
154 R.string.add_games_warning_description, 159 R.string.add_games_warning_description,
155 R.string.add_games_warning_help, 160 R.string.add_games_warning_help,
156 { 161 {
157 val preferences = 162 val preferences =
158 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 163 PreferenceManager.getDefaultSharedPreferences(
164 YuzuApplication.appContext
165 )
159 preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() 166 preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
160 } 167 }
161 ) 168 )
@@ -260,7 +267,9 @@ class SetupFragment : Fragment() {
260 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 267 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
261 private val permissionLauncher = 268 private val permissionLauncher =
262 registerForActivityResult(ActivityResultContracts.RequestPermission()) { 269 registerForActivityResult(ActivityResultContracts.RequestPermission()) {
263 if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { 270 if (!it &&
271 !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
272 ) {
264 PermissionDeniedDialogFragment().show( 273 PermissionDeniedDialogFragment().show(
265 childFragmentManager, 274 childFragmentManager,
266 PermissionDeniedDialogFragment.TAG 275 PermissionDeniedDialogFragment.TAG
@@ -315,7 +324,9 @@ class SetupFragment : Fragment() {
315 } 324 }
316 325
317 private fun setInsets() = 326 private fun setInsets() =
318 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> 327 ViewCompat.setOnApplyWindowInsetsListener(
328 binding.root
329 ) { view: View, windowInsets: WindowInsetsCompat ->
319 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 330 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
320 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 331 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
321 view.setPadding( 332 view.setPadding(
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
index be5e4c86c..bdd6ea628 100644
--- 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
@@ -44,7 +44,9 @@ class AutofitGridLayoutManager(
44 override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { 44 override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) {
45 val width = width 45 val width = width
46 val height = height 46 val height = height
47 if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) { 47 if (columnWidth > 0 && width > 0 && height > 0 &&
48 (isColumnWidthChanged || lastWidth != width || lastHeight != height)
49 ) {
48 val totalSpace: Int = if (orientation == VERTICAL) { 50 val totalSpace: Int = if (orientation == VERTICAL) {
49 width - paddingRight - paddingLeft 51 width - paddingRight - paddingLeft
50 } else { 52 } else {
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
index 35d8000c5..6a048e39f 100644
--- 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
@@ -4,9 +4,9 @@
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import android.os.Parcelable 6import android.os.Parcelable
7import java.util.HashSet
7import kotlinx.parcelize.Parcelize 8import kotlinx.parcelize.Parcelize
8import kotlinx.serialization.Serializable 9import kotlinx.serialization.Serializable
9import java.util.HashSet
10 10
11@Parcelize 11@Parcelize
12@Serializable 12@Serializable
@@ -23,8 +23,9 @@ class Game(
23 val keyLastPlayedTime get() = "${gameId}_LastPlayed" 23 val keyLastPlayedTime get() = "${gameId}_LastPlayed"
24 24
25 override fun equals(other: Any?): Boolean { 25 override fun equals(other: Any?): Boolean {
26 if (other !is Game) 26 if (other !is Game) {
27 return false 27 return false
28 }
28 29
29 return hashCode() == other.hashCode() 30 return hashCode() == other.hashCode()
30 } 31 }
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
index d9b301210..1fe42f922 100644
--- 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
@@ -10,6 +10,7 @@ import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 10import androidx.lifecycle.ViewModel
11import androidx.lifecycle.viewModelScope 11import androidx.lifecycle.viewModelScope
12import androidx.preference.PreferenceManager 12import androidx.preference.PreferenceManager
13import java.util.Locale
13import kotlinx.coroutines.Dispatchers 14import kotlinx.coroutines.Dispatchers
14import kotlinx.coroutines.launch 15import kotlinx.coroutines.launch
15import kotlinx.coroutines.withContext 16import kotlinx.coroutines.withContext
@@ -20,7 +21,6 @@ import kotlinx.serialization.json.Json
20import org.yuzu.yuzu_emu.NativeLibrary 21import org.yuzu.yuzu_emu.NativeLibrary
21import org.yuzu.yuzu_emu.YuzuApplication 22import org.yuzu.yuzu_emu.YuzuApplication
22import org.yuzu.yuzu_emu.utils.GameHelper 23import org.yuzu.yuzu_emu.utils.GameHelper
23import java.util.Locale
24 24
25@OptIn(ExperimentalSerializationApi::class) 25@OptIn(ExperimentalSerializationApi::class)
26class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
@@ -99,8 +99,9 @@ class GamesViewModel : ViewModel() {
99 } 99 }
100 100
101 fun reloadGames(directoryChanged: Boolean) { 101 fun reloadGames(directoryChanged: Boolean) {
102 if (isReloading.value == true) 102 if (isReloading.value == true) {
103 return 103 return
104 }
104 _isReloading.postValue(true) 105 _isReloading.postValue(true)
105 106
106 viewModelScope.launch { 107 viewModelScope.launch {
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
index aa424c768..6251ec783 100644
--- 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
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.overlay
6import android.app.Activity 6import android.app.Activity
7import android.content.Context 7import android.content.Context
8import android.content.SharedPreferences 8import android.content.SharedPreferences
9import android.content.res.Configuration
10import android.graphics.Bitmap 9import android.graphics.Bitmap
11import android.graphics.Canvas 10import android.graphics.Canvas
12import android.graphics.Point 11import android.graphics.Point
@@ -24,6 +23,8 @@ import android.view.WindowInsets
24import androidx.core.content.ContextCompat 23import androidx.core.content.ContextCompat
25import androidx.preference.PreferenceManager 24import androidx.preference.PreferenceManager
26import androidx.window.layout.WindowMetricsCalculator 25import androidx.window.layout.WindowMetricsCalculator
26import kotlin.math.max
27import kotlin.math.min
27import org.yuzu.yuzu_emu.NativeLibrary 28import org.yuzu.yuzu_emu.NativeLibrary
28import org.yuzu.yuzu_emu.NativeLibrary.ButtonType 29import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
29import org.yuzu.yuzu_emu.NativeLibrary.StickType 30import org.yuzu.yuzu_emu.NativeLibrary.StickType
@@ -31,14 +32,13 @@ import org.yuzu.yuzu_emu.R
31import org.yuzu.yuzu_emu.YuzuApplication 32import org.yuzu.yuzu_emu.YuzuApplication
32import org.yuzu.yuzu_emu.features.settings.model.Settings 33import org.yuzu.yuzu_emu.features.settings.model.Settings
33import org.yuzu.yuzu_emu.utils.EmulationMenuSettings 34import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
34import kotlin.math.max
35import kotlin.math.min
36 35
37/** 36/**
38 * Draws the interactive input overlay on top of the 37 * Draws the interactive input overlay on top of the
39 * [SurfaceView] that is rendering emulation. 38 * [SurfaceView] that is rendering emulation.
40 */ 39 */
41class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), 40class InputOverlay(context: Context, attrs: AttributeSet?) :
41 SurfaceView(context, attrs),
42 OnTouchListener { 42 OnTouchListener {
43 private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() 43 private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
44 private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() 44 private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
@@ -51,12 +51,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
51 51
52 private lateinit var windowInsets: WindowInsets 52 private lateinit var windowInsets: WindowInsets
53 53
54 var orientation = LANDSCAPE
55
54 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 56 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
55 super.onLayout(changed, left, top, right, bottom) 57 super.onLayout(changed, left, top, right, bottom)
56 58
57 windowInsets = rootWindowInsets 59 windowInsets = rootWindowInsets
58 60
59 if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { 61 if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
60 defaultOverlay() 62 defaultOverlay()
61 } 63 }
62 64
@@ -93,7 +95,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
93 95
94 var shouldUpdateView = false 96 var shouldUpdateView = false
95 val playerIndex = 97 val playerIndex =
96 if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device 98 if (NativeLibrary.isHandheldOnly()) {
99 NativeLibrary.ConsoleDevice
100 } else {
101 NativeLibrary.Player1Device
102 }
97 103
98 for (button in overlayButtons) { 104 for (button in overlayButtons) {
99 if (!button.updateStatus(event)) { 105 if (!button.updateStatus(event)) {
@@ -156,8 +162,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
156 shouldUpdateView = true 162 shouldUpdateView = true
157 } 163 }
158 164
159 if (shouldUpdateView) 165 if (shouldUpdateView) {
160 invalidate() 166 invalidate()
167 }
161 168
162 if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { 169 if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) {
163 return true 170 return true
@@ -233,10 +240,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
233 val fingerPositionX = event.getX(pointerIndex).toInt() 240 val fingerPositionX = event.getX(pointerIndex).toInt()
234 val fingerPositionY = event.getY(pointerIndex).toInt() 241 val fingerPositionY = event.getY(pointerIndex).toInt()
235 242
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) { 243 for (button in overlayButtons) {
241 // Determine the button state to apply based on the MotionEvent action flag. 244 // Determine the button state to apply based on the MotionEvent action flag.
242 when (event.action and MotionEvent.ACTION_MASK) { 245 when (event.action and MotionEvent.ACTION_MASK) {
@@ -245,9 +248,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
245 // If no button is being moved now, remember the currently touched button to move. 248 // If no button is being moved now, remember the currently touched button to move.
246 if (buttonBeingConfigured == null && 249 if (buttonBeingConfigured == null &&
247 button.bounds.contains( 250 button.bounds.contains(
248 fingerPositionX, 251 fingerPositionX,
249 fingerPositionY 252 fingerPositionY
250 ) 253 )
251 ) { 254 ) {
252 buttonBeingConfigured = button 255 buttonBeingConfigured = button
253 buttonBeingConfigured!!.onConfigureTouch(event) 256 buttonBeingConfigured!!.onConfigureTouch(event)
@@ -266,7 +269,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
266 buttonBeingConfigured!!.buttonId, 269 buttonBeingConfigured!!.buttonId,
267 buttonBeingConfigured!!.bounds.centerX(), 270 buttonBeingConfigured!!.bounds.centerX(),
268 buttonBeingConfigured!!.bounds.centerY(), 271 buttonBeingConfigured!!.bounds.centerY(),
269 "" 272 orientation
270 ) 273 )
271 buttonBeingConfigured = null 274 buttonBeingConfigured = null
272 } 275 }
@@ -299,7 +302,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
299 dpadBeingConfigured!!.upId, 302 dpadBeingConfigured!!.upId,
300 dpadBeingConfigured!!.bounds.centerX(), 303 dpadBeingConfigured!!.bounds.centerX(),
301 dpadBeingConfigured!!.bounds.centerY(), 304 dpadBeingConfigured!!.bounds.centerY(),
302 "" 305 orientation
303 ) 306 )
304 dpadBeingConfigured = null 307 dpadBeingConfigured = null
305 } 308 }
@@ -311,9 +314,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
311 MotionEvent.ACTION_DOWN, 314 MotionEvent.ACTION_DOWN,
312 MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && 315 MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
313 joystick.bounds.contains( 316 joystick.bounds.contains(
314 fingerPositionX, 317 fingerPositionX,
315 fingerPositionY 318 fingerPositionY
316 ) 319 )
317 ) { 320 ) {
318 joystickBeingConfigured = joystick 321 joystickBeingConfigured = joystick
319 joystickBeingConfigured!!.onConfigureTouch(event) 322 joystickBeingConfigured!!.onConfigureTouch(event)
@@ -330,7 +333,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
330 joystickBeingConfigured!!.buttonId, 333 joystickBeingConfigured!!.buttonId,
331 joystickBeingConfigured!!.bounds.centerX(), 334 joystickBeingConfigured!!.bounds.centerX(),
332 joystickBeingConfigured!!.bounds.centerY(), 335 joystickBeingConfigured!!.bounds.centerY(),
333 "" 336 orientation
334 ) 337 )
335 joystickBeingConfigured = null 338 joystickBeingConfigured = null
336 } 339 }
@@ -533,8 +536,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
533 overlayButtons.clear() 536 overlayButtons.clear()
534 overlayDpads.clear() 537 overlayDpads.clear()
535 overlayJoysticks.clear() 538 overlayJoysticks.clear()
536 val orientation =
537 if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
538 539
539 // Add all the enabled overlay items back to the HashSet. 540 // Add all the enabled overlay items back to the HashSet.
540 if (EmulationMenuSettings.showOverlay) { 541 if (EmulationMenuSettings.showOverlay) {
@@ -548,8 +549,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
548 val min = windowSize.first 549 val min = windowSize.first
549 val max = windowSize.second 550 val max = windowSize.second
550 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 551 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
551 .putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x) 552 .putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x)
552 .putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y) 553 .putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y)
553 .apply() 554 .apply()
554 } 555 }
555 556
@@ -558,145 +559,250 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
558 } 559 }
559 560
560 private fun defaultOverlay() { 561 private fun defaultOverlay() {
561 if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { 562 if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
562 defaultOverlayLandscape() 563 defaultOverlayByLayout(orientation)
563 } 564 }
564 565
565 resetButtonPlacement() 566 resetButtonPlacement()
566 preferences.edit() 567 preferences.edit()
567 .putBoolean(Settings.PREF_OVERLAY_INIT, true) 568 .putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true)
568 .apply() 569 .apply()
569 } 570 }
570 571
571 fun resetButtonPlacement() { 572 fun resetButtonPlacement() {
572 defaultOverlayLandscape() 573 defaultOverlayByLayout(orientation)
573 refreshControls() 574 refreshControls()
574 } 575 }
575 576
576 private fun defaultOverlayLandscape() { 577 private val landscapeResources = arrayOf(
578 R.integer.SWITCH_BUTTON_A_X,
579 R.integer.SWITCH_BUTTON_A_Y,
580 R.integer.SWITCH_BUTTON_B_X,
581 R.integer.SWITCH_BUTTON_B_Y,
582 R.integer.SWITCH_BUTTON_X_X,
583 R.integer.SWITCH_BUTTON_X_Y,
584 R.integer.SWITCH_BUTTON_Y_X,
585 R.integer.SWITCH_BUTTON_Y_Y,
586 R.integer.SWITCH_TRIGGER_ZL_X,
587 R.integer.SWITCH_TRIGGER_ZL_Y,
588 R.integer.SWITCH_TRIGGER_ZR_X,
589 R.integer.SWITCH_TRIGGER_ZR_Y,
590 R.integer.SWITCH_BUTTON_DPAD_X,
591 R.integer.SWITCH_BUTTON_DPAD_Y,
592 R.integer.SWITCH_TRIGGER_L_X,
593 R.integer.SWITCH_TRIGGER_L_Y,
594 R.integer.SWITCH_TRIGGER_R_X,
595 R.integer.SWITCH_TRIGGER_R_Y,
596 R.integer.SWITCH_BUTTON_PLUS_X,
597 R.integer.SWITCH_BUTTON_PLUS_Y,
598 R.integer.SWITCH_BUTTON_MINUS_X,
599 R.integer.SWITCH_BUTTON_MINUS_Y,
600 R.integer.SWITCH_BUTTON_HOME_X,
601 R.integer.SWITCH_BUTTON_HOME_Y,
602 R.integer.SWITCH_BUTTON_CAPTURE_X,
603 R.integer.SWITCH_BUTTON_CAPTURE_Y,
604 R.integer.SWITCH_STICK_R_X,
605 R.integer.SWITCH_STICK_R_Y,
606 R.integer.SWITCH_STICK_L_X,
607 R.integer.SWITCH_STICK_L_Y
608 )
609
610 private val portraitResources = arrayOf(
611 R.integer.SWITCH_BUTTON_A_X_PORTRAIT,
612 R.integer.SWITCH_BUTTON_A_Y_PORTRAIT,
613 R.integer.SWITCH_BUTTON_B_X_PORTRAIT,
614 R.integer.SWITCH_BUTTON_B_Y_PORTRAIT,
615 R.integer.SWITCH_BUTTON_X_X_PORTRAIT,
616 R.integer.SWITCH_BUTTON_X_Y_PORTRAIT,
617 R.integer.SWITCH_BUTTON_Y_X_PORTRAIT,
618 R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT,
619 R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT,
620 R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT,
621 R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT,
622 R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT,
623 R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT,
624 R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT,
625 R.integer.SWITCH_TRIGGER_L_X_PORTRAIT,
626 R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT,
627 R.integer.SWITCH_TRIGGER_R_X_PORTRAIT,
628 R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT,
629 R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT,
630 R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT,
631 R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT,
632 R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT,
633 R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT,
634 R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT,
635 R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT,
636 R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT,
637 R.integer.SWITCH_STICK_R_X_PORTRAIT,
638 R.integer.SWITCH_STICK_R_Y_PORTRAIT,
639 R.integer.SWITCH_STICK_L_X_PORTRAIT,
640 R.integer.SWITCH_STICK_L_Y_PORTRAIT
641 )
642
643 private val foldableResources = arrayOf(
644 R.integer.SWITCH_BUTTON_A_X_FOLDABLE,
645 R.integer.SWITCH_BUTTON_A_Y_FOLDABLE,
646 R.integer.SWITCH_BUTTON_B_X_FOLDABLE,
647 R.integer.SWITCH_BUTTON_B_Y_FOLDABLE,
648 R.integer.SWITCH_BUTTON_X_X_FOLDABLE,
649 R.integer.SWITCH_BUTTON_X_Y_FOLDABLE,
650 R.integer.SWITCH_BUTTON_Y_X_FOLDABLE,
651 R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE,
652 R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE,
653 R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE,
654 R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE,
655 R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE,
656 R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE,
657 R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE,
658 R.integer.SWITCH_TRIGGER_L_X_FOLDABLE,
659 R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE,
660 R.integer.SWITCH_TRIGGER_R_X_FOLDABLE,
661 R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE,
662 R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE,
663 R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE,
664 R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE,
665 R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE,
666 R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE,
667 R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE,
668 R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE,
669 R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE,
670 R.integer.SWITCH_STICK_R_X_FOLDABLE,
671 R.integer.SWITCH_STICK_R_Y_FOLDABLE,
672 R.integer.SWITCH_STICK_L_X_FOLDABLE,
673 R.integer.SWITCH_STICK_L_Y_FOLDABLE
674 )
675
676 private fun getResourceValue(orientation: String, position: Int): Float {
677 return when (orientation) {
678 PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
679 FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
680 else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000
681 }
682 }
683
684 private fun defaultOverlayByLayout(orientation: String) {
577 // Each value represents the position of the button in relation to the screen size without insets. 685 // Each value represents the position of the button in relation to the screen size without insets.
578 preferences.edit() 686 preferences.edit()
579 .putFloat( 687 .putFloat(
580 ButtonType.BUTTON_A.toString() + "-X", 688 ButtonType.BUTTON_A.toString() + "-X$orientation",
581 resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 689 getResourceValue(orientation, 0)
582 ) 690 )
583 .putFloat( 691 .putFloat(
584 ButtonType.BUTTON_A.toString() + "-Y", 692 ButtonType.BUTTON_A.toString() + "-Y$orientation",
585 resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 693 getResourceValue(orientation, 1)
586 ) 694 )
587 .putFloat( 695 .putFloat(
588 ButtonType.BUTTON_B.toString() + "-X", 696 ButtonType.BUTTON_B.toString() + "-X$orientation",
589 resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 697 getResourceValue(orientation, 2)
590 ) 698 )
591 .putFloat( 699 .putFloat(
592 ButtonType.BUTTON_B.toString() + "-Y", 700 ButtonType.BUTTON_B.toString() + "-Y$orientation",
593 resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 701 getResourceValue(orientation, 3)
594 ) 702 )
595 .putFloat( 703 .putFloat(
596 ButtonType.BUTTON_X.toString() + "-X", 704 ButtonType.BUTTON_X.toString() + "-X$orientation",
597 resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 705 getResourceValue(orientation, 4)
598 ) 706 )
599 .putFloat( 707 .putFloat(
600 ButtonType.BUTTON_X.toString() + "-Y", 708 ButtonType.BUTTON_X.toString() + "-Y$orientation",
601 resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 709 getResourceValue(orientation, 5)
602 ) 710 )
603 .putFloat( 711 .putFloat(
604 ButtonType.BUTTON_Y.toString() + "-X", 712 ButtonType.BUTTON_Y.toString() + "-X$orientation",
605 resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 713 getResourceValue(orientation, 6)
606 ) 714 )
607 .putFloat( 715 .putFloat(
608 ButtonType.BUTTON_Y.toString() + "-Y", 716 ButtonType.BUTTON_Y.toString() + "-Y$orientation",
609 resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 717 getResourceValue(orientation, 7)
610 ) 718 )
611 .putFloat( 719 .putFloat(
612 ButtonType.TRIGGER_ZL.toString() + "-X", 720 ButtonType.TRIGGER_ZL.toString() + "-X$orientation",
613 resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 721 getResourceValue(orientation, 8)
614 ) 722 )
615 .putFloat( 723 .putFloat(
616 ButtonType.TRIGGER_ZL.toString() + "-Y", 724 ButtonType.TRIGGER_ZL.toString() + "-Y$orientation",
617 resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 725 getResourceValue(orientation, 9)
618 ) 726 )
619 .putFloat( 727 .putFloat(
620 ButtonType.TRIGGER_ZR.toString() + "-X", 728 ButtonType.TRIGGER_ZR.toString() + "-X$orientation",
621 resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 729 getResourceValue(orientation, 10)
622 ) 730 )
623 .putFloat( 731 .putFloat(
624 ButtonType.TRIGGER_ZR.toString() + "-Y", 732 ButtonType.TRIGGER_ZR.toString() + "-Y$orientation",
625 resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 733 getResourceValue(orientation, 11)
626 ) 734 )
627 .putFloat( 735 .putFloat(
628 ButtonType.DPAD_UP.toString() + "-X", 736 ButtonType.DPAD_UP.toString() + "-X$orientation",
629 resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 737 getResourceValue(orientation, 12)
630 ) 738 )
631 .putFloat( 739 .putFloat(
632 ButtonType.DPAD_UP.toString() + "-Y", 740 ButtonType.DPAD_UP.toString() + "-Y$orientation",
633 resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 741 getResourceValue(orientation, 13)
634 ) 742 )
635 .putFloat( 743 .putFloat(
636 ButtonType.TRIGGER_L.toString() + "-X", 744 ButtonType.TRIGGER_L.toString() + "-X$orientation",
637 resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 745 getResourceValue(orientation, 14)
638 ) 746 )
639 .putFloat( 747 .putFloat(
640 ButtonType.TRIGGER_L.toString() + "-Y", 748 ButtonType.TRIGGER_L.toString() + "-Y$orientation",
641 resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 749 getResourceValue(orientation, 15)
642 ) 750 )
643 .putFloat( 751 .putFloat(
644 ButtonType.TRIGGER_R.toString() + "-X", 752 ButtonType.TRIGGER_R.toString() + "-X$orientation",
645 resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 753 getResourceValue(orientation, 16)
646 ) 754 )
647 .putFloat( 755 .putFloat(
648 ButtonType.TRIGGER_R.toString() + "-Y", 756 ButtonType.TRIGGER_R.toString() + "-Y$orientation",
649 resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 757 getResourceValue(orientation, 17)
650 ) 758 )
651 .putFloat( 759 .putFloat(
652 ButtonType.BUTTON_PLUS.toString() + "-X", 760 ButtonType.BUTTON_PLUS.toString() + "-X$orientation",
653 resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 761 getResourceValue(orientation, 18)
654 ) 762 )
655 .putFloat( 763 .putFloat(
656 ButtonType.BUTTON_PLUS.toString() + "-Y", 764 ButtonType.BUTTON_PLUS.toString() + "-Y$orientation",
657 resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 765 getResourceValue(orientation, 19)
658 ) 766 )
659 .putFloat( 767 .putFloat(
660 ButtonType.BUTTON_MINUS.toString() + "-X", 768 ButtonType.BUTTON_MINUS.toString() + "-X$orientation",
661 resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 769 getResourceValue(orientation, 20)
662 ) 770 )
663 .putFloat( 771 .putFloat(
664 ButtonType.BUTTON_MINUS.toString() + "-Y", 772 ButtonType.BUTTON_MINUS.toString() + "-Y$orientation",
665 resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 773 getResourceValue(orientation, 21)
666 ) 774 )
667 .putFloat( 775 .putFloat(
668 ButtonType.BUTTON_HOME.toString() + "-X", 776 ButtonType.BUTTON_HOME.toString() + "-X$orientation",
669 resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 777 getResourceValue(orientation, 22)
670 ) 778 )
671 .putFloat( 779 .putFloat(
672 ButtonType.BUTTON_HOME.toString() + "-Y", 780 ButtonType.BUTTON_HOME.toString() + "-Y$orientation",
673 resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 781 getResourceValue(orientation, 23)
674 ) 782 )
675 .putFloat( 783 .putFloat(
676 ButtonType.BUTTON_CAPTURE.toString() + "-X", 784 ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation",
677 resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X) 785 getResourceValue(orientation, 24)
678 .toFloat() / 1000
679 ) 786 )
680 .putFloat( 787 .putFloat(
681 ButtonType.BUTTON_CAPTURE.toString() + "-Y", 788 ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation",
682 resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y) 789 getResourceValue(orientation, 25)
683 .toFloat() / 1000
684 ) 790 )
685 .putFloat( 791 .putFloat(
686 ButtonType.STICK_R.toString() + "-X", 792 ButtonType.STICK_R.toString() + "-X$orientation",
687 resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 793 getResourceValue(orientation, 26)
688 ) 794 )
689 .putFloat( 795 .putFloat(
690 ButtonType.STICK_R.toString() + "-Y", 796 ButtonType.STICK_R.toString() + "-Y$orientation",
691 resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 797 getResourceValue(orientation, 27)
692 ) 798 )
693 .putFloat( 799 .putFloat(
694 ButtonType.STICK_L.toString() + "-X", 800 ButtonType.STICK_L.toString() + "-X$orientation",
695 resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 801 getResourceValue(orientation, 28)
696 ) 802 )
697 .putFloat( 803 .putFloat(
698 ButtonType.STICK_L.toString() + "-Y", 804 ButtonType.STICK_L.toString() + "-Y$orientation",
699 resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 805 getResourceValue(orientation, 29)
700 ) 806 )
701 .apply() 807 .apply()
702 } 808 }
@@ -709,13 +815,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
709 private val preferences: SharedPreferences = 815 private val preferences: SharedPreferences =
710 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 816 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
711 817
818 const val LANDSCAPE = ""
819 const val PORTRAIT = "_Portrait"
820 const val FOLDABLE = "_Foldable"
821
712 /** 822 /**
713 * Resizes a [Bitmap] by a given scale factor 823 * Resizes a [Bitmap] by a given scale factor
714 * 824 *
715 * @param context Context for getting the vector drawable 825 * @param context Context for getting the vector drawable
716 * @param drawableId The ID of the drawable to scale. 826 * @param drawableId The ID of the drawable to scale.
717 * @param scale The scale factor for the bitmap. 827 * @param scale The scale factor for the bitmap.
718 * @return The scaled [Bitmap] 828 * @return The scaled [Bitmap]
719 */ 829 */
720 private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap { 830 private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap {
721 val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable 831 val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable
@@ -749,14 +859,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
749 * Gets the safe screen size for drawing the overlay 859 * Gets the safe screen size for drawing the overlay
750 * 860 *
751 * @param context Context for getting the window metrics 861 * @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, 862 * @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 863 * the second being the bottom right corner of the safe area
754 */ 864 */
755 private fun getSafeScreenSize(context: Context): Pair<Point, Point> { 865 private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
756 // Get screen size 866 // Get screen size
757 val windowMetrics = 867 val windowMetrics = WindowMetricsCalculator.getOrCreate()
758 WindowMetricsCalculator.getOrCreate() 868 .computeCurrentWindowMetrics(context as Activity)
759 .computeCurrentWindowMetrics(context as Activity)
760 var maxY = windowMetrics.bounds.height().toFloat() 869 var maxY = windowMetrics.bounds.height().toFloat()
761 var maxX = windowMetrics.bounds.width().toFloat() 870 var maxX = windowMetrics.bounds.width().toFloat()
762 var minY = 0 871 var minY = 0
@@ -768,10 +877,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
768 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 877 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
769 val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout 878 val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
770 if (insets != null) { 879 if (insets != null) {
771 if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) 880 if (insets.boundingRectTop.bottom != 0 &&
772 insets.boundingRectTop.bottom.toFloat() else maxY 881 insets.boundingRectTop.bottom > maxY / 2
773 if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) 882 ) {
774 insets.boundingRectRight.left.toFloat() else maxX 883 maxY = insets.boundingRectTop.bottom.toFloat()
884 }
885 if (insets.boundingRectRight.left != 0 &&
886 insets.boundingRectRight.left > maxX / 2
887 ) {
888 maxX = insets.boundingRectRight.left.toFloat()
889 }
775 890
776 minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left 891 minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
777 minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom 892 minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
@@ -878,8 +993,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
878 993
879 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. 994 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
880 // These were set in the input overlay configuration menu. 995 // These were set in the input overlay configuration menu.
881 val xKey = "$buttonId$orientation-X" 996 val xKey = "$buttonId-X$orientation"
882 val yKey = "$buttonId$orientation-Y" 997 val yKey = "$buttonId-Y$orientation"
883 val drawableXPercent = sPrefs.getFloat(xKey, 0f) 998 val drawableXPercent = sPrefs.getFloat(xKey, 0f)
884 val drawableYPercent = sPrefs.getFloat(yKey, 0f) 999 val drawableYPercent = sPrefs.getFloat(yKey, 0f)
885 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1000 val drawableX = (drawableXPercent * max.x + min.x).toInt()
@@ -959,8 +1074,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
959 1074
960 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. 1075 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
961 // These were set in the input overlay configuration menu. 1076 // These were set in the input overlay configuration menu.
962 val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f) 1077 val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f)
963 val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f) 1078 val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f)
964 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1079 val drawableX = (drawableXPercent * max.x + min.x).toInt()
965 val drawableY = (drawableYPercent * max.y + min.y).toInt() 1080 val drawableY = (drawableYPercent * max.y + min.y).toInt()
966 val width = overlayDrawable.width 1081 val width = overlayDrawable.width
@@ -1026,8 +1141,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
1026 1141
1027 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. 1142 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
1028 // These were set in the input overlay configuration menu. 1143 // These were set in the input overlay configuration menu.
1029 val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f) 1144 val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f)
1030 val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f) 1145 val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f)
1031 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1146 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1032 val drawableY = (drawableYPercent * max.y + min.y).toInt() 1147 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1033 val outerScale = 1.66f 1148 val outerScale = 1.66f
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
index 43d664d21..8aef6f5a5 100644
--- 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
@@ -133,7 +133,10 @@ class InputOverlayDrawableDpad(
133 downButtonState = axisY > VIRT_AXIS_DEADZONE 133 downButtonState = axisY > VIRT_AXIS_DEADZONE
134 leftButtonState = axisX < -VIRT_AXIS_DEADZONE 134 leftButtonState = axisX < -VIRT_AXIS_DEADZONE
135 rightButtonState = axisX > VIRT_AXIS_DEADZONE 135 rightButtonState = axisX > VIRT_AXIS_DEADZONE
136 return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState 136 return oldUpState != upButtonState ||
137 oldDownState != downButtonState ||
138 oldLeftState != leftButtonState ||
139 oldRightState != rightButtonState
137 } 140 }
138 return false 141 return false
139 } 142 }
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
index f1d32192a..fb48f584d 100644
--- 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
@@ -9,12 +9,12 @@ import android.graphics.Canvas
9import android.graphics.Rect 9import android.graphics.Rect
10import android.graphics.drawable.BitmapDrawable 10import android.graphics.drawable.BitmapDrawable
11import android.view.MotionEvent 11import android.view.MotionEvent
12import org.yuzu.yuzu_emu.NativeLibrary
13import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
14import kotlin.math.atan2 12import kotlin.math.atan2
15import kotlin.math.cos 13import kotlin.math.cos
16import kotlin.math.sin 14import kotlin.math.sin
17import kotlin.math.sqrt 15import kotlin.math.sqrt
16import org.yuzu.yuzu_emu.NativeLibrary
17import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
18 18
19/** 19/**
20 * Custom [BitmapDrawable] that is capable 20 * Custom [BitmapDrawable] that is capable
@@ -241,14 +241,22 @@ class InputOverlayDrawableJoystick(
241 private fun setInnerBounds() { 241 private fun setInnerBounds() {
242 var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() 242 var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
243 var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() 243 var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
244 if (x > virtBounds.centerX() + virtBounds.width() / 2) x = 244 if (x > virtBounds.centerX() + virtBounds.width() / 2) {
245 virtBounds.centerX() + virtBounds.width() / 2 245 x =
246 if (x < virtBounds.centerX() - virtBounds.width() / 2) x = 246 virtBounds.centerX() + virtBounds.width() / 2
247 virtBounds.centerX() - virtBounds.width() / 2 247 }
248 if (y > virtBounds.centerY() + virtBounds.height() / 2) y = 248 if (x < virtBounds.centerX() - virtBounds.width() / 2) {
249 virtBounds.centerY() + virtBounds.height() / 2 249 x =
250 if (y < virtBounds.centerY() - virtBounds.height() / 2) y = 250 virtBounds.centerX() - virtBounds.width() / 2
251 virtBounds.centerY() - virtBounds.height() / 2 251 }
252 if (y > virtBounds.centerY() + virtBounds.height() / 2) {
253 y =
254 virtBounds.centerY() + virtBounds.height() / 2
255 }
256 if (y < virtBounds.centerY() - virtBounds.height() / 2) {
257 y =
258 virtBounds.centerY() - virtBounds.height() / 2
259 }
252 val width = pressedStateInnerBitmap.bounds.width() / 2 260 val width = pressedStateInnerBitmap.bounds.width() / 2
253 val height = pressedStateInnerBitmap.bounds.height() / 2 261 val height = pressedStateInnerBitmap.bounds.height() / 2
254 defaultStateInnerBitmap.setBounds( 262 defaultStateInnerBitmap.setBounds(
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
index 97eef40d2..b0156dca5 100644
--- 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
@@ -99,7 +99,9 @@ class GamesFragment : Fragment() {
99 } 99 }
100 shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> 100 shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
101 if (shouldSwapData) { 101 if (shouldSwapData) {
102 (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!) 102 (binding.gridGames.adapter as GameAdapter).submitList(
103 gamesViewModel.games.value!!
104 )
103 gamesViewModel.setShouldSwapData(false) 105 gamesViewModel.setShouldSwapData(false)
104 } 106 }
105 } 107 }
@@ -128,7 +130,9 @@ class GamesFragment : Fragment() {
128 } 130 }
129 131
130 private fun setInsets() = 132 private fun setInsets() =
131 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> 133 ViewCompat.setOnApplyWindowInsetsListener(
134 binding.root
135 ) { view: View, windowInsets: WindowInsetsCompat ->
132 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 136 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
133 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 137 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
134 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large) 138 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
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
index 041d16f3a..cc1d87f1b 100644
--- 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
@@ -26,6 +26,9 @@ import androidx.preference.PreferenceManager
26import com.google.android.material.color.MaterialColors 26import com.google.android.material.color.MaterialColors
27import com.google.android.material.dialog.MaterialAlertDialogBuilder 27import com.google.android.material.dialog.MaterialAlertDialogBuilder
28import com.google.android.material.navigation.NavigationBarView 28import com.google.android.material.navigation.NavigationBarView
29import java.io.File
30import java.io.FilenameFilter
31import java.io.IOException
29import kotlinx.coroutines.Dispatchers 32import kotlinx.coroutines.Dispatchers
30import kotlinx.coroutines.launch 33import kotlinx.coroutines.launch
31import kotlinx.coroutines.withContext 34import kotlinx.coroutines.withContext
@@ -43,9 +46,6 @@ import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
43import org.yuzu.yuzu_emu.model.GamesViewModel 46import org.yuzu.yuzu_emu.model.GamesViewModel
44import org.yuzu.yuzu_emu.model.HomeViewModel 47import org.yuzu.yuzu_emu.model.HomeViewModel
45import org.yuzu.yuzu_emu.utils.* 48import org.yuzu.yuzu_emu.utils.*
46import java.io.File
47import java.io.FilenameFilter
48import java.io.IOException
49 49
50class MainActivity : AppCompatActivity(), ThemeProvider { 50class MainActivity : AppCompatActivity(), ThemeProvider {
51 private lateinit var binding: ActivityMainBinding 51 private lateinit var binding: ActivityMainBinding
@@ -86,7 +86,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
86 ThemeHelper.SYSTEM_BAR_ALPHA 86 ThemeHelper.SYSTEM_BAR_ALPHA
87 ) 87 )
88 ) 88 )
89 if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { 89 if (InsetsHelper.getSystemGestureType(applicationContext) !=
90 InsetsHelper.GESTURE_NAVIGATION
91 ) {
90 binding.navigationBarShade.setBackgroundColor( 92 binding.navigationBarShade.setBackgroundColor(
91 ThemeHelper.getColorWithOpacity( 93 ThemeHelper.getColorWithOpacity(
92 MaterialColors.getColor( 94 MaterialColors.getColor(
@@ -172,7 +174,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
172 binding.navigationView.height.toFloat() * 2 174 binding.navigationView.height.toFloat() * 2
173 translationY(0f) 175 translationY(0f)
174 } else { 176 } else {
175 if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { 177 if (ViewCompat.getLayoutDirection(binding.navigationView) ==
178 ViewCompat.LAYOUT_DIRECTION_LTR
179 ) {
176 binding.navigationView.translationX = 180 binding.navigationView.translationX =
177 binding.navigationView.width.toFloat() * -2 181 binding.navigationView.width.toFloat() * -2
178 translationX(0f) 182 translationX(0f)
@@ -189,7 +193,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
189 if (smallLayout) { 193 if (smallLayout) {
190 translationY(binding.navigationView.height.toFloat() * 2) 194 translationY(binding.navigationView.height.toFloat() * 2)
191 } else { 195 } else {
192 if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { 196 if (ViewCompat.getLayoutDirection(binding.navigationView) ==
197 ViewCompat.LAYOUT_DIRECTION_LTR
198 ) {
193 translationX(binding.navigationView.width.toFloat() * -2) 199 translationX(binding.navigationView.width.toFloat() * -2)
194 } else { 200 } else {
195 translationX(binding.navigationView.width.toFloat() * 2) 201 translationX(binding.navigationView.width.toFloat() * 2)
@@ -234,7 +240,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
234 } 240 }
235 241
236 private fun setInsets() = 242 private fun setInsets() =
237 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> 243 ViewCompat.setOnApplyWindowInsetsListener(
244 binding.root
245 ) { _: View, windowInsets: WindowInsetsCompat ->
238 val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 246 val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
239 val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams 247 val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams
240 mlpStatusShade.height = insets.top 248 mlpStatusShade.height = insets.top
@@ -256,8 +264,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
256 264
257 val getGamesDirectory = 265 val getGamesDirectory =
258 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> 266 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
259 if (result == null) 267 if (result == null) {
260 return@registerForActivityResult 268 return@registerForActivityResult
269 }
261 270
262 contentResolver.takePersistableUriPermission( 271 contentResolver.takePersistableUriPermission(
263 result, 272 result,
@@ -281,8 +290,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
281 290
282 val getProdKey = 291 val getProdKey =
283 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 292 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
284 if (result == null) 293 if (result == null) {
285 return@registerForActivityResult 294 return@registerForActivityResult
295 }
286 296
287 if (!FileUtil.hasExtension(result, "keys")) { 297 if (!FileUtil.hasExtension(result, "keys")) {
288 MessageDialogFragment.newInstance( 298 MessageDialogFragment.newInstance(
@@ -324,8 +334,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
324 334
325 val getFirmware = 335 val getFirmware =
326 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 336 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
327 if (result == null) 337 if (result == null) {
328 return@registerForActivityResult 338 return@registerForActivityResult
339 }
329 340
330 val inputZip = contentResolver.openInputStream(result) 341 val inputZip = contentResolver.openInputStream(result)
331 if (inputZip == null) { 342 if (inputZip == null) {
@@ -376,8 +387,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
376 387
377 val getAmiiboKey = 388 val getAmiiboKey =
378 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 389 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
379 if (result == null) 390 if (result == null) {
380 return@registerForActivityResult 391 return@registerForActivityResult
392 }
381 393
382 if (!FileUtil.hasExtension(result, "bin")) { 394 if (!FileUtil.hasExtension(result, "bin")) {
383 MessageDialogFragment.newInstance( 395 MessageDialogFragment.newInstance(
@@ -418,8 +430,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
418 430
419 val getDriver = 431 val getDriver =
420 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 432 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
421 if (result == null) 433 if (result == null) {
422 return@registerForActivityResult 434 return@registerForActivityResult
435 }
423 436
424 val takeFlags = 437 val takeFlags =
425 Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION 438 Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -470,8 +483,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
470 483
471 val installGameUpdate = 484 val installGameUpdate =
472 registerForActivityResult(ActivityResultContracts.OpenDocument()) { 485 registerForActivityResult(ActivityResultContracts.OpenDocument()) {
473 if (it == null) 486 if (it == null) {
474 return@registerForActivityResult 487 return@registerForActivityResult
488 }
475 489
476 IndeterminateProgressDialogFragment.newInstance( 490 IndeterminateProgressDialogFragment.newInstance(
477 this@MainActivity, 491 this@MainActivity,
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
index 791cea904..eeefcdf20 100644
--- 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
@@ -19,7 +19,9 @@ class ControllerMappingHelper {
19 // The two analog triggers generate analog motion events as well as a keycode. 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 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 21 keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
22 } else false 22 } else {
23 false
24 }
23 } 25 }
24 26
25 /** 27 /**
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
index 36c479e6c..2ee63697e 100644
--- 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
@@ -4,8 +4,8 @@
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context 6import android.content.Context
7import org.yuzu.yuzu_emu.NativeLibrary
8import java.io.IOException 7import java.io.IOException
8import org.yuzu.yuzu_emu.NativeLibrary
9 9
10object DirectoryInitialization { 10object DirectoryInitialization {
11 private var userPath: String? = null 11 private var userPath: String? = null
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
index cc8ea6b9d..cf226ad94 100644
--- 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
@@ -5,10 +5,10 @@ package org.yuzu.yuzu_emu.utils
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import org.yuzu.yuzu_emu.YuzuApplication
9import org.yuzu.yuzu_emu.model.MinimalDocumentFile
10import java.io.File 8import java.io.File
11import java.util.* 9import java.util.*
10import org.yuzu.yuzu_emu.YuzuApplication
11import org.yuzu.yuzu_emu.model.MinimalDocumentFile
12 12
13class DocumentsTree { 13class DocumentsTree {
14 private var root: DocumentsNode? = null 14 private var root: DocumentsNode? = null
@@ -29,13 +29,20 @@ class DocumentsTree {
29 val node = resolvePath(filepath) 29 val node = resolvePath(filepath)
30 return if (node == null || node.isDirectory) { 30 return if (node == null || node.isDirectory) {
31 0 31 0
32 } else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) 32 } else {
33 FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
34 }
33 } 35 }
34 36
35 fun exists(filepath: String): Boolean { 37 fun exists(filepath: String): Boolean {
36 return resolvePath(filepath) != null 38 return resolvePath(filepath) != null
37 } 39 }
38 40
41 fun isDirectory(filepath: String): Boolean {
42 val node = resolvePath(filepath)
43 return node != null && node.isDirectory
44 }
45
39 private fun resolvePath(filepath: String): DocumentsNode? { 46 private fun resolvePath(filepath: String): DocumentsNode? {
40 val tokens = StringTokenizer(filepath, File.separator, false) 47 val tokens = StringTokenizer(filepath, File.separator, false)
41 var iterator = root 48 var iterator = root
@@ -106,7 +113,9 @@ class DocumentsTree {
106 fun isNativePath(path: String): Boolean { 113 fun isNativePath(path: String): Boolean {
107 return if (path.isNotEmpty()) { 114 return if (path.isNotEmpty()) {
108 path[0] == '/' 115 path[0] == '/'
109 } else false 116 } else {
117 false
118 }
110 } 119 }
111 } 120 }
112} 121}
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
index e1e7a59d7..7e8f058c1 100644
--- 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
@@ -11,14 +11,6 @@ object EmulationMenuSettings {
11 private val preferences = 11 private val preferences =
12 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 12 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
13 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 14 var joystickRelCenter: Boolean
23 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true) 15 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
24 set(value) { 16 set(value) {
@@ -41,16 +33,6 @@ object EmulationMenuSettings {
41 .apply() 33 .apply()
42 } 34 }
43 35
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 36 var showFps: Boolean
55 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false) 37 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
56 set(value) { 38 set(value) {
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
index 492b1ad91..9f3bbe56f 100644
--- 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
@@ -9,8 +9,6 @@ import android.net.Uri
9import android.provider.DocumentsContract 9import android.provider.DocumentsContract
10import android.provider.OpenableColumns 10import android.provider.OpenableColumns
11import androidx.documentfile.provider.DocumentFile 11import androidx.documentfile.provider.DocumentFile
12import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.model.MinimalDocumentFile
14import java.io.BufferedInputStream 12import java.io.BufferedInputStream
15import java.io.File 13import java.io.File
16import java.io.FileOutputStream 14import java.io.FileOutputStream
@@ -19,6 +17,8 @@ import java.io.InputStream
19import java.net.URLDecoder 17import java.net.URLDecoder
20import java.util.zip.ZipEntry 18import java.util.zip.ZipEntry
21import java.util.zip.ZipInputStream 19import java.util.zip.ZipInputStream
20import org.yuzu.yuzu_emu.YuzuApplication
21import org.yuzu.yuzu_emu.model.MinimalDocumentFile
22 22
23object FileUtil { 23object FileUtil {
24 const val PATH_TREE = "tree" 24 const val PATH_TREE = "tree"
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
index dc9b7c744..086d17606 100644
--- 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
@@ -54,7 +54,7 @@ class ForegroundService : Service() {
54 54
55 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 55 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
56 if (intent == null) { 56 if (intent == null) {
57 return START_NOT_STICKY; 57 return START_NOT_STICKY
58 } 58 }
59 if (intent.action == ACTION_STOP) { 59 if (intent.action == ACTION_STOP) {
60 NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) 60 NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
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
index 42b207618..ee9f3e570 100644
--- 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
@@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.utils
6import android.content.SharedPreferences 6import android.content.SharedPreferences
7import android.net.Uri 7import android.net.Uri
8import androidx.preference.PreferenceManager 8import androidx.preference.PreferenceManager
9import java.util.*
9import kotlinx.serialization.encodeToString 10import kotlinx.serialization.encodeToString
10import kotlinx.serialization.json.Json 11import kotlinx.serialization.json.Json
11import org.yuzu.yuzu_emu.NativeLibrary 12import org.yuzu.yuzu_emu.NativeLibrary
12import org.yuzu.yuzu_emu.YuzuApplication 13import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.model.Game 14import org.yuzu.yuzu_emu.model.Game
14import java.util.*
15 15
16object GameHelper { 16object GameHelper {
17 const val KEY_GAME_PATH = "game_path" 17 const val KEY_GAME_PATH = "game_path"
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
index 528011d7f..1d4695a2a 100644
--- 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
@@ -5,14 +5,14 @@ package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context 6import android.content.Context
7import android.net.Uri 7import android.net.Uri
8import org.yuzu.yuzu_emu.NativeLibrary
9import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
10import java.io.BufferedInputStream 8import java.io.BufferedInputStream
11import java.io.File 9import java.io.File
12import java.io.FileInputStream 10import java.io.FileInputStream
13import java.io.FileOutputStream 11import java.io.FileOutputStream
14import java.io.IOException 12import java.io.IOException
15import java.util.zip.ZipInputStream 13import java.util.zip.ZipInputStream
14import org.yuzu.yuzu_emu.NativeLibrary
15import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
16 16
17object GpuDriverHelper { 17object GpuDriverHelper {
18 private const val META_JSON_FILENAME = "meta.json" 18 private const val META_JSON_FILENAME = "meta.json"
@@ -113,6 +113,8 @@ object GpuDriverHelper {
113 initializeDriverParameters(context) 113 initializeDriverParameters(context)
114 } 114 }
115 115
116 external fun supportsCustomDriverLoading(): Boolean
117
116 // Parse the custom driver metadata to retrieve the name. 118 // Parse the custom driver metadata to retrieve the name.
117 val customDriverName: String? 119 val customDriverName: String?
118 get() { 120 get() {
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
index 70bdb4097..a4e64070a 100644
--- 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
@@ -3,12 +3,12 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import org.json.JSONException
7import org.json.JSONObject
8import java.io.IOException 6import java.io.IOException
9import java.nio.charset.StandardCharsets 7import java.nio.charset.StandardCharsets
10import java.nio.file.Files 8import java.nio.file.Files
11import java.nio.file.Paths 9import java.nio.file.Paths
10import org.json.JSONException
11import org.json.JSONObject
12 12
13class GpuDriverMetadata(metadataFilePath: String) { 13class GpuDriverMetadata(metadataFilePath: String) {
14 var name: String? = null 14 var name: String? = null
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
index 24e999b29..e963dfbc1 100644
--- 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
@@ -5,8 +5,8 @@ package org.yuzu.yuzu_emu.utils
5 5
6import android.view.KeyEvent 6import android.view.KeyEvent
7import android.view.MotionEvent 7import android.view.MotionEvent
8import org.yuzu.yuzu_emu.NativeLibrary
9import kotlin.math.sqrt 8import kotlin.math.sqrt
9import org.yuzu.yuzu_emu.NativeLibrary
10 10
11class InputHandler { 11class InputHandler {
12 fun initialize() { 12 fun initialize() {
@@ -68,7 +68,11 @@ class InputHandler {
68 6 -> NativeLibrary.Player6Device 68 6 -> NativeLibrary.Player6Device
69 7 -> NativeLibrary.Player7Device 69 7 -> NativeLibrary.Player7Device
70 8 -> NativeLibrary.Player8Device 70 8 -> NativeLibrary.Player8Device
71 else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device 71 else -> if (NativeLibrary.isHandheldOnly()) {
72 NativeLibrary.ConsoleDevice
73 } else {
74 NativeLibrary.Player1Device
75 }
72 } 76 }
73 } 77 }
74 78
@@ -107,7 +111,11 @@ class InputHandler {
107 } 111 }
108 112
109 private fun getAxisToButton(axis: Float): Int { 113 private fun getAxisToButton(axis: Float): Int {
110 return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED 114 return if (axis > 0.5f) {
115 NativeLibrary.ButtonState.PRESSED
116 } else {
117 NativeLibrary.ButtonState.RELEASED
118 }
111 } 119 }
112 120
113 private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { 121 private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
@@ -287,7 +295,6 @@ class InputHandler {
287 } 295 }
288 } 296 }
289 297
290
291 private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { 298 private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
292 // Joycon support is half dead. Right joystick doesn't work 299 // Joycon support is half dead. Right joystick doesn't work
293 val playerNumber = getPlayerNumber(event.device.controllerNumber) 300 val playerNumber = getPlayerNumber(event.device.controllerNumber)
@@ -355,6 +362,4 @@ class InputHandler {
355 ) 362 )
356 } 363 }
357 } 364 }
358 365}
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
index 19c53c481..595f0d284 100644
--- 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
@@ -4,9 +4,7 @@
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.annotation.SuppressLint 6import android.annotation.SuppressLint
7import android.app.Activity
8import android.content.Context 7import android.content.Context
9import android.graphics.Rect
10 8
11object InsetsHelper { 9object InsetsHelper {
12 const val THREE_BUTTON_NAVIGATION = 0 10 const val THREE_BUTTON_NAVIGATION = 0
@@ -20,12 +18,8 @@ object InsetsHelper {
20 resources.getIdentifier("config_navBarInteractionMode", "integer", "android") 18 resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
21 return if (resourceId != 0) { 19 return if (resourceId != 0) {
22 resources.getInteger(resourceId) 20 resources.getInteger(resourceId)
23 } else 0 21 } else {
24 } 22 0
25 23 }
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 } 24 }
31} 25}
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
index 344dd8a0a..68ed66565 100644
--- 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
@@ -13,8 +13,8 @@ import android.nfc.tech.NfcA
13import android.os.Build 13import android.os.Build
14import android.os.Handler 14import android.os.Handler
15import android.os.Looper 15import android.os.Looper
16import org.yuzu.yuzu_emu.NativeLibrary
17import java.io.IOException 16import java.io.IOException
17import org.yuzu.yuzu_emu.NativeLibrary
18 18
19class NfcReader(private val activity: Activity) { 19class NfcReader(private val activity: Activity) {
20 private var nfcAdapter: NfcAdapter? = null 20 private var nfcAdapter: NfcAdapter? = null
@@ -25,10 +25,13 @@ class NfcReader(private val activity: Activity) {
25 25
26 pendingIntent = PendingIntent.getActivity( 26 pendingIntent = PendingIntent.getActivity(
27 activity, 27 activity,
28 0, Intent(activity, activity.javaClass), 28 0,
29 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) 29 Intent(activity, activity.javaClass),
30 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
30 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE 31 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
31 else PendingIntent.FLAG_UPDATE_CURRENT 32 } else {
33 PendingIntent.FLAG_UPDATE_CURRENT
34 }
32 ) 35 )
33 36
34 val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) 37 val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
@@ -45,9 +48,9 @@ class NfcReader(private val activity: Activity) {
45 48
46 fun onNewIntent(intent: Intent) { 49 fun onNewIntent(intent: Intent) {
47 val action = intent.action 50 val action = intent.action
48 if (NfcAdapter.ACTION_TAG_DISCOVERED != action 51 if (NfcAdapter.ACTION_TAG_DISCOVERED != action &&
49 && NfcAdapter.ACTION_TECH_DISCOVERED != action 52 NfcAdapter.ACTION_TECH_DISCOVERED != action &&
50 && NfcAdapter.ACTION_NDEF_DISCOVERED != action 53 NfcAdapter.ACTION_NDEF_DISCOVERED != action
51 ) { 54 ) {
52 return 55 return
53 } 56 }
@@ -84,7 +87,7 @@ class NfcReader(private val activity: Activity) {
84 } 87 }
85 88
86 private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { 89 private fun ntag215ReadAll(amiibo: NfcA): ByteArray? {
87 val bufferSize = amiibo.maxTransceiveLength; 90 val bufferSize = amiibo.maxTransceiveLength
88 val tagSize = 0x21C 91 val tagSize = 0x21C
89 val pageSize = 4 92 val pageSize = 4
90 val lastPage = tagSize / pageSize - 1 93 val lastPage = tagSize / pageSize - 1
@@ -103,7 +106,7 @@ class NfcReader(private val activity: Activity) {
103 val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) 106 val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1)
104 System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) 107 System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize)
105 } catch (e: IOException) { 108 } catch (e: IOException) {
106 return null; 109 return null
107 } 110 }
108 } 111 }
109 return tagData 112 return tagData
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
index 87ee7f2e6..00e58faec 100644
--- 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
@@ -11,30 +11,34 @@ import java.io.Serializable
11 11
12object SerializableHelper { 12object SerializableHelper {
13 inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { 13 inline fun <reified T : Serializable> Bundle.serializable(key: String): T? {
14 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 14 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
15 getSerializable(key, T::class.java) 15 getSerializable(key, T::class.java)
16 else 16 } else {
17 getSerializable(key) as? T 17 getSerializable(key) as? T
18 }
18 } 19 }
19 20
20 inline fun <reified T : Serializable> Intent.serializable(key: String): T? { 21 inline fun <reified T : Serializable> Intent.serializable(key: String): T? {
21 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 22 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
22 getSerializableExtra(key, T::class.java) 23 getSerializableExtra(key, T::class.java)
23 else 24 } else {
24 getSerializableExtra(key) as? T 25 getSerializableExtra(key) as? T
26 }
25 } 27 }
26 28
27 inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? { 29 inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? {
28 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 30 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
29 getParcelable(key, T::class.java) 31 getParcelable(key, T::class.java)
30 else 32 } else {
31 getParcelable(key) as? T 33 getParcelable(key) as? T
34 }
32 } 35 }
33 36
34 inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? { 37 inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? {
35 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 38 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
36 getParcelableExtra(key, T::class.java) 39 getParcelableExtra(key, T::class.java)
37 else 40 } else {
38 getParcelableExtra(key) as? T 41 getParcelableExtra(key) as? T
42 }
39 } 43 }
40} 44}
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
index e55767c0f..f312e24cf 100644
--- 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
@@ -3,21 +3,19 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.app.Activity
7import android.content.res.Configuration 6import android.content.res.Configuration
8import android.graphics.Color 7import android.graphics.Color
9import androidx.annotation.ColorInt 8import androidx.annotation.ColorInt
10import androidx.appcompat.app.AppCompatActivity 9import androidx.appcompat.app.AppCompatActivity
11import androidx.appcompat.app.AppCompatDelegate 10import androidx.appcompat.app.AppCompatDelegate
12import androidx.core.content.ContextCompat
13import androidx.core.view.WindowCompat 11import androidx.core.view.WindowCompat
14import androidx.core.view.WindowInsetsControllerCompat 12import androidx.core.view.WindowInsetsControllerCompat
15import androidx.preference.PreferenceManager 13import androidx.preference.PreferenceManager
14import kotlin.math.roundToInt
16import org.yuzu.yuzu_emu.R 15import org.yuzu.yuzu_emu.R
17import org.yuzu.yuzu_emu.YuzuApplication 16import org.yuzu.yuzu_emu.YuzuApplication
18import org.yuzu.yuzu_emu.features.settings.model.Settings 17import org.yuzu.yuzu_emu.features.settings.model.Settings
19import org.yuzu.yuzu_emu.ui.main.ThemeProvider 18import org.yuzu.yuzu_emu.ui.main.ThemeProvider
20import kotlin.math.roundToInt
21 19
22object ThemeHelper { 20object ThemeHelper {
23 const val SYSTEM_BAR_ALPHA = 0.9f 21 const val SYSTEM_BAR_ALPHA = 0.9f
@@ -36,8 +34,8 @@ object ThemeHelper {
36 // Using a specific night mode check because this could apply incorrectly when using the 34 // 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 35 // 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. 36 // will then show light mode colors/navigation bars but with black backgrounds.
39 if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) 37 if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) &&
40 && isNightMode(activity) 38 isNightMode(activity)
41 ) { 39 ) {
42 activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) 40 activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
43 } 41 }
@@ -46,8 +44,10 @@ object ThemeHelper {
46 @ColorInt 44 @ColorInt
47 fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int { 45 fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int {
48 return Color.argb( 46 return Color.argb(
49 (alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color), 47 (alphaFactor * Color.alpha(color)).roundToInt(),
50 Color.green(color), Color.blue(color) 48 Color.red(color),
49 Color.green(color),
50 Color.blue(color)
51 ) 51 )
52 } 52 }
53 53
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
index d89a89983..685ccaa76 100644
--- 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
@@ -38,8 +38,8 @@ class FixedRatioSurfaceView @JvmOverloads constructor(
38 newWidth = width 38 newWidth = width
39 newHeight = (width / aspectRatio).roundToInt() 39 newHeight = (width / aspectRatio).roundToInt()
40 } 40 }
41 val left = (width - newWidth) / 2; 41 val left = (width - newWidth) / 2
42 val top = (height - newHeight) / 2; 42 val top = (height - newHeight) / 2
43 setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) 43 setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
44 } else { 44 } else {
45 setLeftTopRightBottom(0, 0, width, height) 45 setLeftTopRightBottom(0, 0, width, height)
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 4091c23d1..632aa50b3 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -202,6 +202,11 @@ public:
202 return m_is_running; 202 return m_is_running;
203 } 203 }
204 204
205 bool IsPaused() const {
206 std::scoped_lock lock(m_mutex);
207 return m_is_running && m_is_paused;
208 }
209
205 const Core::PerfStatsResults& PerfStats() const { 210 const Core::PerfStatsResults& PerfStats() const {
206 std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); 211 std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
207 return m_perf_stats; 212 return m_perf_stats;
@@ -287,11 +292,13 @@ public:
287 void PauseEmulation() { 292 void PauseEmulation() {
288 std::scoped_lock lock(m_mutex); 293 std::scoped_lock lock(m_mutex);
289 m_system.Pause(); 294 m_system.Pause();
295 m_is_paused = true;
290 } 296 }
291 297
292 void UnPauseEmulation() { 298 void UnPauseEmulation() {
293 std::scoped_lock lock(m_mutex); 299 std::scoped_lock lock(m_mutex);
294 m_system.Run(); 300 m_system.Run();
301 m_is_paused = false;
295 } 302 }
296 303
297 void HaltEmulation() { 304 void HaltEmulation() {
@@ -473,6 +480,7 @@ private:
473 std::shared_ptr<FileSys::VfsFilesystem> m_vfs; 480 std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
474 Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; 481 Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
475 bool m_is_running{}; 482 bool m_is_running{};
483 bool m_is_paused{};
476 SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; 484 SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
477 std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; 485 std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
478 486
@@ -552,6 +560,26 @@ void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
552 GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir)); 560 GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
553} 561}
554 562
563[[maybe_unused]] static bool CheckKgslPresent() {
564 constexpr auto KgslPath{"/dev/kgsl-3d0"};
565
566 return access(KgslPath, F_OK) == 0;
567}
568
569[[maybe_unused]] bool SupportsCustomDriver() {
570 return android_get_device_api_level() >= 28 && CheckKgslPresent();
571}
572
573jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
574 [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject instance) {
575#ifdef ARCHITECTURE_arm64
576 // If the KGSL device exists custom drivers can be loaded using adrenotools
577 return SupportsCustomDriver();
578#else
579 return false;
580#endif
581}
582
555jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, 583jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
556 [[maybe_unused]] jclass clazz) { 584 [[maybe_unused]] jclass clazz) {
557 Core::Crypto::KeyManager::Instance().ReloadKeys(); 585 Core::Crypto::KeyManager::Instance().ReloadKeys();
@@ -583,6 +611,11 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv
583 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); 611 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
584} 612}
585 613
614jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env,
615 [[maybe_unused]] jclass clazz) {
616 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
617}
618
586jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env, 619jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
587 [[maybe_unused]] jclass clazz) { 620 [[maybe_unused]] jclass clazz) {
588 return EmulationSession::GetInstance().IsHandheldOnly(); 621 return EmulationSession::GetInstance().IsHandheldOnly();
diff --git a/src/android/app/src/main/res/drawable/ic_pip_pause.xml b/src/android/app/src/main/res/drawable/ic_pip_pause.xml
new file mode 100644
index 000000000..4a7d4ea03
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_pip_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="@android:color/white"
8 android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_pip_play.xml b/src/android/app/src/main/res/drawable/ic_pip_play.xml
new file mode 100644
index 000000000..2303a4623
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_pip_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="@android:color/white"
8 android:pathData="M8,5v14l11,-7z" />
9</vector>
diff --git a/src/android/app/src/main/res/layout/activity_emulation.xml b/src/android/app/src/main/res/layout/activity_emulation.xml
index f6360a65b..139065d3d 100644
--- a/src/android/app/src/main/res/layout/activity_emulation.xml
+++ b/src/android/app/src/main/res/layout/activity_emulation.xml
@@ -1,13 +1,9 @@
1<FrameLayout 1<androidx.fragment.app.FragmentContainerView
2 xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:id="@+id/frame_content" 3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/fragment_container"
5 android:name="androidx.navigation.fragment.NavHostFragment"
4 android:layout_width="match_parent" 6 android:layout_width="match_parent"
5 android:layout_height="match_parent" 7 android:layout_height="match_parent"
6 android:keepScreenOn="true"> 8 android:keepScreenOn="true"
7 9 app:defaultNavHost="true" />
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/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml
index 09b789b6b..e54a10e8f 100644
--- a/src/android/app/src/main/res/layout/fragment_emulation.xml
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -12,49 +12,65 @@
12 android:layout_width="match_parent" 12 android:layout_width="match_parent"
13 android:layout_height="match_parent"> 13 android:layout_height="match_parent">
14 14
15 <!-- This is what everything is rendered to during emulation --> 15 <FrameLayout
16 <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView 16 android:id="@+id/emulation_container"
17 android:id="@+id/surface_emulation"
18 android:layout_width="match_parent" 17 android:layout_width="match_parent"
19 android:layout_height="match_parent" 18 android:layout_height="match_parent">
20 android:layout_gravity="center" 19
21 android:focusable="false" 20 <!-- This is what everything is rendered to during emulation -->
22 android:focusableInTouchMode="false" /> 21 <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView
22 android:id="@+id/surface_emulation"
23 android:layout_width="match_parent"
24 android:layout_height="match_parent"
25 android:layout_gravity="center"
26 android:focusable="false"
27 android:focusableInTouchMode="false" />
28
29 </FrameLayout>
23 30
24 <FrameLayout 31 <FrameLayout
25 android:id="@+id/overlay_container" 32 android:id="@+id/input_container"
26 android:layout_width="match_parent" 33 android:layout_width="match_parent"
27 android:layout_height="match_parent" 34 android:layout_height="match_parent"
28 android:layout_gravity="bottom"> 35 android:layout_gravity="bottom">
29 36
30 <!-- This is the onscreen input overlay --> 37 <!-- This is the onscreen input overlay -->
31 <org.yuzu.yuzu_emu.overlay.InputOverlay 38 <org.yuzu.yuzu_emu.overlay.InputOverlay
32 android:id="@+id/surface_input_overlay" 39 android:id="@+id/surface_input_overlay"
40 android:layout_width="match_parent"
41 android:layout_height="match_parent"
42 android:layout_gravity="center"
43 android:focusable="true"
44 android:focusableInTouchMode="true" />
45
46 <Button
47 style="@style/Widget.Material3.Button.ElevatedButton"
48 android:id="@+id/done_control_config"
49 android:layout_width="wrap_content"
50 android:layout_height="wrap_content"
51 android:layout_gravity="center"
52 android:text="@string/emulation_done"
53 android:visibility="gone" />
54
55 </FrameLayout>
56
57 <FrameLayout
58 android:id="@+id/overlay_container"
33 android:layout_width="match_parent" 59 android:layout_width="match_parent"
34 android:layout_height="match_parent" 60 android:layout_height="match_parent">
35 android:focusable="true"
36 android:focusableInTouchMode="true" />
37 61
38 <TextView 62 <TextView
39 android:id="@+id/show_fps_text" 63 android:id="@+id/show_fps_text"
40 android:layout_width="wrap_content" 64 android:layout_width="wrap_content"
41 android:layout_height="wrap_content" 65 android:layout_height="wrap_content"
42 android:layout_gravity="left" 66 android:layout_gravity="left"
43 android:clickable="false" 67 android:clickable="false"
44 android:focusable="false" 68 android:focusable="false"
45 android:shadowColor="@android:color/black" 69 android:shadowColor="@android:color/black"
46 android:textColor="@android:color/white" 70 android:textColor="@android:color/white"
47 android:textSize="12sp" 71 android:textSize="12sp"
48 tools:ignore="RtlHardcoded" /> 72 tools:ignore="RtlHardcoded" />
49 73
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> 74 </FrameLayout>
59 75
60 </androidx.coordinatorlayout.widget.CoordinatorLayout> 76 </androidx.coordinatorlayout.widget.CoordinatorLayout>
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
index 599d845ad..a5767adee 100644
--- 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
@@ -1,16 +1,16 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
3 xmlns:tools="http://schemas.android.com/tools" 4 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent" 5 android:layout_width="match_parent"
5 android:layout_height="wrap_content" 6 android:layout_height="wrap_content"
6 xmlns:app="http://schemas.android.com/apk/res-auto"
7 android:background="?android:attr/selectableItemBackground" 7 android:background="?android:attr/selectableItemBackground"
8 android:clickable="true" 8 android:clickable="true"
9 android:focusable="true" 9 android:focusable="true"
10 android:minHeight="72dp" 10 android:minHeight="72dp"
11 android:paddingVertical="@dimen/spacing_large"
11 android:paddingStart="@dimen/spacing_large" 12 android:paddingStart="@dimen/spacing_large"
12 android:paddingEnd="24dp" 13 android:paddingEnd="24dp">
13 android:paddingVertical="@dimen/spacing_large">
14 14
15 <com.google.android.material.materialswitch.MaterialSwitch 15 <com.google.android.material.materialswitch.MaterialSwitch
16 android:id="@+id/switch_widget" 16 android:id="@+id/switch_widget"
@@ -19,32 +19,35 @@
19 android:layout_alignParentEnd="true" 19 android:layout_alignParentEnd="true"
20 android:layout_centerVertical="true" /> 20 android:layout_centerVertical="true" />
21 21
22 <com.google.android.material.textview.MaterialTextView 22 <LinearLayout
23 style="@style/TextAppearance.Material3.BodySmall" 23 android:layout_width="match_parent"
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" 24 android:layout_height="wrap_content"
41 android:layout_alignParentStart="true"
42 android:layout_alignParentTop="true" 25 android:layout_alignParentTop="true"
26 android:layout_centerVertical="true"
43 android:layout_marginEnd="@dimen/spacing_large" 27 android:layout_marginEnd="@dimen/spacing_large"
44 android:layout_toStartOf="@+id/switch_widget" 28 android:layout_toStartOf="@+id/switch_widget"
45 android:textSize="16sp" 29 android:gravity="center_vertical"
46 android:textAlignment="viewStart" 30 android:orientation="vertical">
47 app:lineHeight="28dp" 31
48 tools:text="@string/frame_limit_enable" /> 32 <com.google.android.material.textview.MaterialTextView
33 android:id="@+id/text_setting_name"
34 style="@style/TextAppearance.Material3.HeadlineMedium"
35 android:layout_width="wrap_content"
36 android:layout_height="wrap_content"
37 android:textAlignment="viewStart"
38 android:textSize="16sp"
39 app:lineHeight="28dp"
40 tools:text="@string/frame_limit_enable" />
41
42 <com.google.android.material.textview.MaterialTextView
43 android:id="@+id/text_setting_description"
44 style="@style/TextAppearance.Material3.BodySmall"
45 android:layout_width="wrap_content"
46 android:layout_height="wrap_content"
47 android:layout_marginTop="@dimen/spacing_small"
48 android:textAlignment="viewStart"
49 tools:text="@string/frame_limit_enable_description" />
50
51 </LinearLayout>
49 52
50</RelativeLayout> 53</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
index abd24df6f..cf85bc0da 100644
--- 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
@@ -1,20 +1,14 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" 2<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools" 3 xmlns:tools="http://schemas.android.com/tools"
4 android:id="@+id/text_header_name"
5 style="@style/TextAppearance.Material3.TitleSmall"
4 android:layout_width="match_parent" 6 android:layout_width="match_parent"
5 android:layout_height="48dp" 7 android:layout_height="wrap_content"
6 android:paddingVertical="4dp" 8 android:layout_gravity="start|center_vertical"
7 android:paddingHorizontal="@dimen/spacing_large"> 9 android:paddingHorizontal="@dimen/spacing_large"
8 10 android:paddingVertical="16dp"
9 <com.google.android.material.textview.MaterialTextView 11 android:textAlignment="viewStart"
10 style="@style/TextAppearance.Material3.TitleSmall" 12 android:textColor="?attr/colorPrimary"
11 android:id="@+id/text_header_name" 13 android:textStyle="bold"
12 android:layout_width="match_parent" 14 tools:text="CPU Settings" />
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/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml
new file mode 100644
index 000000000..8208f4c2c
--- /dev/null
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -0,0 +1,18 @@
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 xmlns:tools="http://schemas.android.com/tools"
5 android:id="@+id/emulation_navigation"
6 app:startDestination="@id/emulationFragment">
7
8 <fragment
9 android:id="@+id/emulationFragment"
10 android:name="org.yuzu.yuzu_emu.fragments.EmulationFragment"
11 android:label="fragment_emulation"
12 tools:layout="@layout/fragment_emulation" >
13 <argument
14 android:name="game"
15 app:argType="org.yuzu.yuzu_emu.model.Game" />
16 </fragment>
17
18</navigation>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 48072683e..fcebba726 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -56,4 +56,18 @@
56 android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment" 56 android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment"
57 android:label="LicensesFragment" /> 57 android:label="LicensesFragment" />
58 58
59 <activity
60 android:id="@+id/emulationActivity"
61 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
62 android:label="EmulationActivity">
63 <argument
64 android:name="game"
65 app:argType="org.yuzu.yuzu_emu.model.Game" />
66 </activity>
67
68 <action
69 android:id="@+id/action_global_emulationActivity"
70 app:destination="@id/emulationActivity"
71 app:launchSingleTop="true" />
72
59</navigation> 73</navigation>
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index ea20cb17c..6d092f7a9 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -119,6 +119,18 @@
119 <item>3</item> 119 <item>3</item>
120 </integer-array> 120 </integer-array>
121 121
122 <string-array name="rendererScreenLayoutNames">
123 <item>@string/screen_layout_landscape</item>
124 <item>@string/screen_layout_portrait</item>
125 <item>@string/screen_layout_auto</item>
126 </string-array>
127
128 <integer-array name="rendererScreenLayoutValues">
129 <item>5</item>
130 <item>4</item>
131 <item>0</item>
132 </integer-array>
133
122 <string-array name="rendererAspectRatioNames"> 134 <string-array name="rendererAspectRatioNames">
123 <item>@string/ratio_default</item> 135 <item>@string/ratio_default</item>
124 <item>@string/ratio_force_four_three</item> 136 <item>@string/ratio_force_four_three</item>
@@ -224,4 +236,15 @@
224 <item>2</item> 236 <item>2</item>
225 </integer-array> 237 </integer-array>
226 238
239 <string-array name="outputEngineEntries">
240 <item>@string/auto</item>
241 <item>@string/cubeb</item>
242 <item>@string/string_null</item>
243 </string-array>
244 <string-array name="outputEngineValues">
245 <item>auto</item>
246 <item>cubeb</item>
247 <item>null</item>
248 </string-array>
249
227</resources> 250</resources>
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
index bc614b81d..2e93b408c 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -34,4 +34,68 @@
34 <integer name="SWITCH_BUTTON_DPAD_X">260</integer> 34 <integer name="SWITCH_BUTTON_DPAD_X">260</integer>
35 <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> 35 <integer name="SWITCH_BUTTON_DPAD_Y">790</integer>
36 36
37 <!-- Default SWITCH portrait layout -->
38 <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer>
39 <integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer>
40 <integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer>
41 <integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer>
42 <integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer>
43 <integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer>
44 <integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer>
45 <integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer>
46 <integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer>
47 <integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer>
48 <integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer>
49 <integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer>
50 <integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer>
51 <integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer>
52 <integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer>
53 <integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer>
54 <integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer>
55 <integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer>
56 <integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer>
57 <integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer>
58 <integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer>
59 <integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer>
60 <integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer>
61 <integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer>
62 <integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer>
63 <integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer>
64 <integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer>
65 <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
66 <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer>
67 <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer>
68
69 <!-- Default SWITCH foldable layout -->
70 <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer>
71 <integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer>
72 <integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer>
73 <integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer>
74 <integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer>
75 <integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer>
76 <integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer>
77 <integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer>
78 <integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer>
79 <integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer>
80 <integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer>
81 <integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer>
82 <integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer>
83 <integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer>
84 <integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer>
85 <integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer>
86 <integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer>
87 <integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer>
88 <integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer>
89 <integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer>
90 <integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer>
91 <integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer>
92 <integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer>
93 <integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer>
94 <integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer>
95 <integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer>
96 <integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer>
97 <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
98 <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer>
99 <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer>
100
37</resources> 101</resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index c236811fa..cc1d8c39d 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<resources> 2<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
3 3
4 <!-- General application strings --> 4 <!-- General application strings -->
5 <string name="app_name" translatable="false">yuzu</string> 5 <string name="app_name" translatable="false">yuzu</string>
@@ -158,10 +158,10 @@
158 <string name="set_custom_rtc">Set custom RTC</string> 158 <string name="set_custom_rtc">Set custom RTC</string>
159 159
160 <!-- Graphics settings strings --> 160 <!-- Graphics settings strings -->
161 <string name="renderer_api">API</string>
162 <string name="renderer_accuracy">Accuracy level</string> 161 <string name="renderer_accuracy">Accuracy level</string>
163 <string name="renderer_resolution">Resolution (Handheld/Docked)</string> 162 <string name="renderer_resolution">Resolution (Handheld/Docked)</string>
164 <string name="renderer_vsync">VSync mode</string> 163 <string name="renderer_vsync">VSync mode</string>
164 <string name="renderer_screen_layout">Orientation</string>
165 <string name="renderer_aspect_ratio">Aspect ratio</string> 165 <string name="renderer_aspect_ratio">Aspect ratio</string>
166 <string name="renderer_scaling_filter">Window adapting filter</string> 166 <string name="renderer_scaling_filter">Window adapting filter</string>
167 <string name="renderer_anti_aliasing">Anti-aliasing method</string> 167 <string name="renderer_anti_aliasing">Anti-aliasing method</string>
@@ -171,12 +171,21 @@
171 <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</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> 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> 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> 174 <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> 175 <string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string>
178 176
177 <!-- Debug settings strings -->
178 <string name="cpu">CPU</string>
179 <string name="cpu_debug_mode">CPU Debugging</string>
180 <string name="cpu_debug_mode_description">Puts the CPU in a slow debugging mode.</string>
181 <string name="gpu">GPU</string>
182 <string name="renderer_api">API</string>
183 <string name="renderer_debug">Graphics debugging</string>
184 <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string>
185 <string name="fastmem">Fastmem</string>
186
179 <!-- Audio settings strings --> 187 <!-- Audio settings strings -->
188 <string name="audio_output_engine">Output engine</string>
180 <string name="audio_volume">Volume</string> 189 <string name="audio_volume">Volume</string>
181 <string name="audio_volume_description">Specifies the volume of audio output.</string> 190 <string name="audio_volume_description">Specifies the volume of audio output.</string>
182 191
@@ -195,6 +204,7 @@
195 <string name="learn_more">Learn more</string> 204 <string name="learn_more">Learn more</string>
196 <string name="auto">Auto</string> 205 <string name="auto">Auto</string>
197 <string name="submit">Submit</string> 206 <string name="submit">Submit</string>
207 <string name="string_null">Null</string>
198 208
199 <!-- GPU driver installation --> 209 <!-- GPU driver installation -->
200 <string name="select_gpu_driver">Select GPU driver</string> 210 <string name="select_gpu_driver">Select GPU driver</string>
@@ -326,6 +336,11 @@
326 <string name="anti_aliasing_fxaa">FXAA</string> 336 <string name="anti_aliasing_fxaa">FXAA</string>
327 <string name="anti_aliasing_smaa">SMAA</string> 337 <string name="anti_aliasing_smaa">SMAA</string>
328 338
339 <!-- Screen Layouts -->
340 <string name="screen_layout_landscape">Landscape</string>
341 <string name="screen_layout_portrait">Portrait</string>
342 <string name="screen_layout_auto">Auto</string>
343
329 <!-- Aspect Ratios --> 344 <!-- Aspect Ratios -->
330 <string name="ratio_default">Default (16:9)</string> 345 <string name="ratio_default">Default (16:9)</string>
331 <string name="ratio_force_four_three">Force 4:3</string> 346 <string name="ratio_force_four_three">Force 4:3</string>
@@ -360,10 +375,19 @@
360 <string name="theme_mode_light">Light</string> 375 <string name="theme_mode_light">Light</string>
361 <string name="theme_mode_dark">Dark</string> 376 <string name="theme_mode_dark">Dark</string>
362 377
378 <!-- Audio output engines -->
379 <string name="cubeb">cubeb</string>
380
363 <!-- Black backgrounds theme --> 381 <!-- Black backgrounds theme -->
364 <string name="use_black_backgrounds">Black backgrounds</string> 382 <string name="use_black_backgrounds">Black backgrounds</string>
365 <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> 383 <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
366 384
385 <!-- Picture-In-Picture -->
386 <string name="picture_in_picture">Picture in Picture</string>
387 <string name="picture_in_picture_description">Minimize window when placed in the background</string>
388 <string name="pause">Pause</string>
389 <string name="play">Play</string>
390
367 <!-- Licenses screen strings --> 391 <!-- Licenses screen strings -->
368 <string name="licenses">Licenses</string> 392 <string name="licenses">Licenses</string>
369 <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string> 393 <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index e19e8ce58..80f370c16 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -11,3 +11,12 @@ plugins {
11tasks.register("clean").configure { 11tasks.register("clean").configure {
12 delete(rootProject.buildDir) 12 delete(rootProject.buildDir)
13} 13}
14
15buildscript {
16 repositories {
17 google()
18 }
19 dependencies {
20 classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0")
21 }
22}
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index e1716c62d..6d66c926d 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -3,6 +3,9 @@
3 3
4#include "common/fs/file.h" 4#include "common/fs/file.h"
5#include "common/fs/fs.h" 5#include "common/fs/fs.h"
6#ifdef ANDROID
7#include "common/fs/fs_android.h"
8#endif
6#include "common/fs/path_util.h" 9#include "common/fs/path_util.h"
7#include "common/logging/log.h" 10#include "common/logging/log.h"
8 11
@@ -525,15 +528,39 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
525// Generic Filesystem Operations 528// Generic Filesystem Operations
526 529
527bool Exists(const fs::path& path) { 530bool Exists(const fs::path& path) {
531#ifdef ANDROID
532 if (Android::IsContentUri(path)) {
533 return Android::Exists(path);
534 } else {
535 return fs::exists(path);
536 }
537#else
528 return fs::exists(path); 538 return fs::exists(path);
539#endif
529} 540}
530 541
531bool IsFile(const fs::path& path) { 542bool IsFile(const fs::path& path) {
543#ifdef ANDROID
544 if (Android::IsContentUri(path)) {
545 return !Android::IsDirectory(path);
546 } else {
547 return fs::is_regular_file(path);
548 }
549#else
532 return fs::is_regular_file(path); 550 return fs::is_regular_file(path);
551#endif
533} 552}
534 553
535bool IsDir(const fs::path& path) { 554bool IsDir(const fs::path& path) {
555#ifdef ANDROID
556 if (Android::IsContentUri(path)) {
557 return Android::IsDirectory(path);
558 } else {
559 return fs::is_directory(path);
560 }
561#else
536 return fs::is_directory(path); 562 return fs::is_directory(path);
563#endif
537} 564}
538 565
539fs::path GetCurrentDir() { 566fs::path GetCurrentDir() {
diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h
index bb8a52648..b441c2a12 100644
--- a/src/common/fs/fs_android.h
+++ b/src/common/fs/fs_android.h
@@ -12,7 +12,10 @@
12 "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") 12 "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I")
13 13
14#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ 14#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \
15 V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") 15 V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") \
16 V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \
17 "(Ljava/lang/String;)Z") \
18 V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z")
16 19
17namespace Common::FS::Android { 20namespace Common::FS::Android {
18 21
diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp
index ceb0b41c6..7c17bbefa 100644
--- a/src/core/file_sys/system_archive/time_zone_binary.cpp
+++ b/src/core/file_sys/system_archive/time_zone_binary.cpp
@@ -15,7 +15,7 @@ namespace FileSys::SystemArchive {
15const static std::map<std::string, const std::map<const char*, const std::vector<u8>>&> 15const static std::map<std::string, const std::map<const char*, const std::vector<u8>>&>
16 tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa}, 16 tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa},
17 {"America", NxTzdb::america}, 17 {"America", NxTzdb::america},
18 {"Antartica", NxTzdb::antartica}, 18 {"Antarctica", NxTzdb::antarctica},
19 {"Arctic", NxTzdb::arctic}, 19 {"Arctic", NxTzdb::arctic},
20 {"Asia", NxTzdb::asia}, 20 {"Asia", NxTzdb::asia},
21 {"Atlantic", NxTzdb::atlantic}, 21 {"Atlantic", NxTzdb::atlantic},
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
index 853b893a1..311a59e5f 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -150,23 +150,29 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
150 while (cur_length > 0 && it != concatenation_map.end()) { 150 while (cur_length > 0 && it != concatenation_map.end()) {
151 // Check if we can read the file at this position. 151 // Check if we can read the file at this position.
152 const auto& file = it->file; 152 const auto& file = it->file;
153 const u64 file_offset = it->offset; 153 const u64 map_offset = it->offset;
154 const u64 file_size = file->GetSize(); 154 const u64 file_size = file->GetSize();
155 155
156 if (cur_offset >= file_offset + file_size) { 156 if (cur_offset > map_offset + file_size) {
157 // Entirely out of bounds read. 157 // Entirely out of bounds read.
158 break; 158 break;
159 } 159 }
160 160
161 // Read the file at this position. 161 // Read the file at this position.
162 const u64 intended_read_size = std::min<u64>(cur_length, file_size); 162 const u64 file_seek = cur_offset - map_offset;
163 const u64 intended_read_size = std::min<u64>(cur_length, file_size - file_seek);
163 const u64 actual_read_size = 164 const u64 actual_read_size =
164 file->Read(data + (cur_offset - offset), intended_read_size, cur_offset - file_offset); 165 file->Read(data + (cur_offset - offset), intended_read_size, file_seek);
165 166
166 // Update tracking. 167 // Update tracking.
167 cur_offset += actual_read_size; 168 cur_offset += actual_read_size;
168 cur_length -= actual_read_size; 169 cur_length -= actual_read_size;
169 it++; 170 it++;
171
172 // If we encountered a short read, we're done.
173 if (actual_read_size < intended_read_size) {
174 break;
175 }
170 } 176 }
171 177
172 return cur_offset - offset; 178 return cur_offset - offset;
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index cc0076238..7a15d8438 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -25,6 +25,8 @@ namespace FS = Common::FS;
25 25
26namespace { 26namespace {
27 27
28constexpr size_t MaxOpenFiles = 512;
29
28constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { 30constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) {
29 switch (mode) { 31 switch (mode) {
30 case Mode::Read: 32 case Mode::Read:
@@ -73,28 +75,30 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
73VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { 75VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
74 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 76 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
75 77
76 if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) { 78 if (auto it = cache.find(path); it != cache.end()) {
77 const auto& weak = weak_iter->second; 79 if (auto file = it->second.lock(); file) {
78 80 return file;
79 if (!weak.expired()) {
80 return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms));
81 } 81 }
82 } 82 }
83 83
84 auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); 84 if (!FS::Exists(path) || !FS::IsFile(path)) {
85
86 if (!backing) {
87 return nullptr; 85 return nullptr;
88 } 86 }
89 87
90 cache.insert_or_assign(path, std::move(backing)); 88 auto reference = std::make_unique<FileReference>();
89 this->InsertReferenceIntoList(*reference);
91 90
92 // Cannot use make_shared as RealVfsFile constructor is private 91 auto file =
93 return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); 92 std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms));
93 cache[path] = file;
94
95 return file;
94} 96}
95 97
96VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { 98VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
97 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 99 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
100 cache.erase(path);
101
98 // Current usages of CreateFile expect to delete the contents of an existing file. 102 // Current usages of CreateFile expect to delete the contents of an existing file.
99 if (FS::IsFile(path)) { 103 if (FS::IsFile(path)) {
100 FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; 104 FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile};
@@ -123,51 +127,22 @@ VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_
123VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { 127VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
124 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); 128 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
125 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); 129 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
126 const auto cached_file_iter = cache.find(old_path); 130 cache.erase(old_path);
127 131 cache.erase(new_path);
128 if (cached_file_iter != cache.cend()) { 132 if (!FS::RenameFile(old_path, new_path)) {
129 auto file = cached_file_iter->second.lock();
130
131 if (!cached_file_iter->second.expired()) {
132 file->Close();
133 }
134
135 if (!FS::RenameFile(old_path, new_path)) {
136 return nullptr;
137 }
138
139 cache.erase(old_path);
140 file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile);
141 if (file->IsOpen()) {
142 cache.insert_or_assign(new_path, std::move(file));
143 } else {
144 LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path);
145 }
146 } else {
147 ASSERT(false);
148 return nullptr; 133 return nullptr;
149 } 134 }
150
151 return OpenFile(new_path, Mode::ReadWrite); 135 return OpenFile(new_path, Mode::ReadWrite);
152} 136}
153 137
154bool RealVfsFilesystem::DeleteFile(std::string_view path_) { 138bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
155 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 139 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
156 const auto cached_iter = cache.find(path); 140 cache.erase(path);
157
158 if (cached_iter != cache.cend()) {
159 if (!cached_iter->second.expired()) {
160 cached_iter->second.lock()->Close();
161 }
162 cache.erase(path);
163 }
164
165 return FS::RemoveFile(path); 141 return FS::RemoveFile(path);
166} 142}
167 143
168VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { 144VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
169 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 145 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
170 // Cannot use make_shared as RealVfsDirectory constructor is private
171 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); 146 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
172} 147}
173 148
@@ -176,7 +151,6 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms
176 if (!FS::CreateDirs(path)) { 151 if (!FS::CreateDirs(path)) {
177 return nullptr; 152 return nullptr;
178 } 153 }
179 // Cannot use make_shared as RealVfsDirectory constructor is private
180 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); 154 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
181} 155}
182 156
@@ -194,73 +168,102 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
194 if (!FS::RenameDir(old_path, new_path)) { 168 if (!FS::RenameDir(old_path, new_path)) {
195 return nullptr; 169 return nullptr;
196 } 170 }
171 return OpenDirectory(new_path, Mode::ReadWrite);
172}
197 173
198 for (auto& kv : cache) { 174bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
199 // If the path in the cache doesn't start with old_path, then bail on this file. 175 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
200 if (kv.first.rfind(old_path, 0) != 0) { 176 return FS::RemoveDirRecursively(path);
201 continue; 177}
202 }
203 178
204 const auto file_old_path = 179void RealVfsFilesystem::RefreshReference(const std::string& path, Mode perms,
205 FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); 180 FileReference& reference) {
206 auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()), 181 // Temporarily remove from list.
207 FS::DirectorySeparator::PlatformDefault); 182 this->RemoveReferenceFromList(reference);
208 const auto& cached = cache[file_old_path];
209 183
210 if (cached.expired()) { 184 // Restore file if needed.
211 continue; 185 if (!reference.file) {
212 } 186 this->EvictSingleReference();
213 187
214 auto file = cached.lock(); 188 reference.file =
215 cache.erase(file_old_path); 189 FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
216 file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); 190 if (reference.file) {
217 if (file->IsOpen()) { 191 num_open_files++;
218 cache.insert_or_assign(std::move(file_new_path), std::move(file));
219 } else {
220 LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path);
221 } 192 }
222 } 193 }
223 194
224 return OpenDirectory(new_path, Mode::ReadWrite); 195 // Reinsert into list.
196 this->InsertReferenceIntoList(reference);
225} 197}
226 198
227bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { 199void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) {
228 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 200 // Remove from list.
201 this->RemoveReferenceFromList(*reference);
229 202
230 for (auto& kv : cache) { 203 // Close the file.
231 // If the path in the cache doesn't start with path, then bail on this file. 204 if (reference->file) {
232 if (kv.first.rfind(path, 0) != 0) { 205 reference->file.reset();
233 continue; 206 num_open_files--;
234 } 207 }
208}
235 209
236 const auto& entry = cache[kv.first]; 210void RealVfsFilesystem::EvictSingleReference() {
237 if (!entry.expired()) { 211 if (num_open_files < MaxOpenFiles || open_references.empty()) {
238 entry.lock()->Close(); 212 return;
239 } 213 }
214
215 // Get and remove from list.
216 auto& reference = open_references.back();
217 this->RemoveReferenceFromList(reference);
240 218
241 cache.erase(kv.first); 219 // Close the file.
220 if (reference.file) {
221 reference.file.reset();
222 num_open_files--;
242 } 223 }
243 224
244 return FS::RemoveDirRecursively(path); 225 // Reinsert into closed list.
226 this->InsertReferenceIntoList(reference);
227}
228
229void RealVfsFilesystem::InsertReferenceIntoList(FileReference& reference) {
230 if (reference.file) {
231 open_references.push_front(reference);
232 } else {
233 closed_references.push_front(reference);
234 }
235}
236
237void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) {
238 if (reference.file) {
239 open_references.erase(open_references.iterator_to(reference));
240 } else {
241 closed_references.erase(closed_references.iterator_to(reference));
242 }
245} 243}
246 244
247RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, 245RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
248 const std::string& path_, Mode perms_) 246 const std::string& path_, Mode perms_)
249 : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), 247 : base(base_), reference(std::move(reference_)), path(path_),
250 path_components(FS::SplitPathComponents(path_)), perms(perms_) {} 248 parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)),
249 perms(perms_) {}
251 250
252RealVfsFile::~RealVfsFile() = default; 251RealVfsFile::~RealVfsFile() {
252 base.DropReference(std::move(reference));
253}
253 254
254std::string RealVfsFile::GetName() const { 255std::string RealVfsFile::GetName() const {
255 return path_components.back(); 256 return path_components.back();
256} 257}
257 258
258std::size_t RealVfsFile::GetSize() const { 259std::size_t RealVfsFile::GetSize() const {
259 return backing->GetSize(); 260 base.RefreshReference(path, perms, *reference);
261 return reference->file ? reference->file->GetSize() : 0;
260} 262}
261 263
262bool RealVfsFile::Resize(std::size_t new_size) { 264bool RealVfsFile::Resize(std::size_t new_size) {
263 return backing->SetSize(new_size); 265 base.RefreshReference(path, perms, *reference);
266 return reference->file ? reference->file->SetSize(new_size) : false;
264} 267}
265 268
266VirtualDir RealVfsFile::GetContainingDirectory() const { 269VirtualDir RealVfsFile::GetContainingDirectory() const {
@@ -276,27 +279,25 @@ bool RealVfsFile::IsReadable() const {
276} 279}
277 280
278std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { 281std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
279 if (!backing->Seek(static_cast<s64>(offset))) { 282 base.RefreshReference(path, perms, *reference);
283 if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
280 return 0; 284 return 0;
281 } 285 }
282 return backing->ReadSpan(std::span{data, length}); 286 return reference->file->ReadSpan(std::span{data, length});
283} 287}
284 288
285std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { 289std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
286 if (!backing->Seek(static_cast<s64>(offset))) { 290 base.RefreshReference(path, perms, *reference);
291 if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
287 return 0; 292 return 0;
288 } 293 }
289 return backing->WriteSpan(std::span{data, length}); 294 return reference->file->WriteSpan(std::span{data, length});
290} 295}
291 296
292bool RealVfsFile::Rename(std::string_view name) { 297bool RealVfsFile::Rename(std::string_view name) {
293 return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; 298 return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr;
294} 299}
295 300
296void RealVfsFile::Close() {
297 backing->Close();
298}
299
300// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if 301// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if
301// constexpr' because there is a compile error in the branch not used. 302// constexpr' because there is a compile error in the branch not used.
302 303
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index b92c84316..d8c900e33 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -3,8 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <map>
6#include <string_view> 7#include <string_view>
7#include <boost/container/flat_map.hpp> 8#include "common/intrusive_list.h"
8#include "core/file_sys/mode.h" 9#include "core/file_sys/mode.h"
9#include "core/file_sys/vfs.h" 10#include "core/file_sys/vfs.h"
10 11
@@ -14,6 +15,11 @@ class IOFile;
14 15
15namespace FileSys { 16namespace FileSys {
16 17
18struct FileReference : public Common::IntrusiveListBaseNode<FileReference> {
19 std::shared_ptr<Common::FS::IOFile> file{};
20};
21
22class RealVfsFile;
17class RealVfsFilesystem : public VfsFilesystem { 23class RealVfsFilesystem : public VfsFilesystem {
18public: 24public:
19 RealVfsFilesystem(); 25 RealVfsFilesystem();
@@ -35,7 +41,21 @@ public:
35 bool DeleteDirectory(std::string_view path) override; 41 bool DeleteDirectory(std::string_view path) override;
36 42
37private: 43private:
38 boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache; 44 using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
45 std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
46 ReferenceListType open_references;
47 ReferenceListType closed_references;
48 size_t num_open_files{};
49
50private:
51 friend class RealVfsFile;
52 void RefreshReference(const std::string& path, Mode perms, FileReference& reference);
53 void DropReference(std::unique_ptr<FileReference>&& reference);
54 void EvictSingleReference();
55
56private:
57 void InsertReferenceIntoList(FileReference& reference);
58 void RemoveReferenceFromList(FileReference& reference);
39}; 59};
40 60
41// An implementation of VfsFile that represents a file on the user's computer. 61// An implementation of VfsFile that represents a file on the user's computer.
@@ -57,13 +77,11 @@ public:
57 bool Rename(std::string_view name) override; 77 bool Rename(std::string_view name) override;
58 78
59private: 79private:
60 RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, 80 RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
61 const std::string& path, Mode perms = Mode::Read); 81 const std::string& path, Mode perms = Mode::Read);
62 82
63 void Close();
64
65 RealVfsFilesystem& base; 83 RealVfsFilesystem& base;
66 std::shared_ptr<Common::FS::IOFile> backing; 84 std::unique_ptr<FileReference> reference;
67 std::string path; 85 std::string path;
68 std::string parent_path; 86 std::string parent_path;
69 std::vector<std::string> path_components; 87 std::vector<std::string> path_components;
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index 70480b725..908811e2c 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -4,6 +4,8 @@
4#include <algorithm> 4#include <algorithm>
5#include <atomic> 5#include <atomic>
6#include <cinttypes> 6#include <cinttypes>
7#include <condition_variable>
8#include <mutex>
7#include <optional> 9#include <optional>
8#include <vector> 10#include <vector>
9 11
@@ -1313,7 +1315,8 @@ void KThread::RequestDummyThreadWait() {
1313 ASSERT(this->IsDummyThread()); 1315 ASSERT(this->IsDummyThread());
1314 1316
1315 // We will block when the scheduler lock is released. 1317 // We will block when the scheduler lock is released.
1316 m_dummy_thread_runnable.store(false); 1318 std::scoped_lock lock{m_dummy_thread_mutex};
1319 m_dummy_thread_runnable = false;
1317} 1320}
1318 1321
1319void KThread::DummyThreadBeginWait() { 1322void KThread::DummyThreadBeginWait() {
@@ -1323,7 +1326,8 @@ void KThread::DummyThreadBeginWait() {
1323 } 1326 }
1324 1327
1325 // Block until runnable is no longer false. 1328 // Block until runnable is no longer false.
1326 m_dummy_thread_runnable.wait(false); 1329 std::unique_lock lock{m_dummy_thread_mutex};
1330 m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; });
1327} 1331}
1328 1332
1329void KThread::DummyThreadEndWait() { 1333void KThread::DummyThreadEndWait() {
@@ -1331,8 +1335,11 @@ void KThread::DummyThreadEndWait() {
1331 ASSERT(this->IsDummyThread()); 1335 ASSERT(this->IsDummyThread());
1332 1336
1333 // Wake up the waiting thread. 1337 // Wake up the waiting thread.
1334 m_dummy_thread_runnable.store(true); 1338 {
1335 m_dummy_thread_runnable.notify_one(); 1339 std::scoped_lock lock{m_dummy_thread_mutex};
1340 m_dummy_thread_runnable = true;
1341 }
1342 m_dummy_thread_cv.notify_one();
1336} 1343}
1337 1344
1338void KThread::BeginWait(KThreadQueue* queue) { 1345void KThread::BeginWait(KThreadQueue* queue) {
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index f9814ac8f..37fe5db77 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -892,7 +892,9 @@ private:
892 std::shared_ptr<Common::Fiber> m_host_context{}; 892 std::shared_ptr<Common::Fiber> m_host_context{};
893 ThreadType m_thread_type{}; 893 ThreadType m_thread_type{};
894 StepState m_step_state{}; 894 StepState m_step_state{};
895 std::atomic<bool> m_dummy_thread_runnable{true}; 895 bool m_dummy_thread_runnable{true};
896 std::mutex m_dummy_thread_mutex{};
897 std::condition_variable m_dummy_thread_cv{};
896 898
897 // For debugging 899 // For debugging
898 std::vector<KSynchronizationObject*> m_wait_objects_for_debugging{}; 900 std::vector<KSynchronizationObject*> m_wait_objects_for_debugging{};
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.cpp b/src/core/hle/service/nfc/common/amiibo_crypto.cpp
index b2bcb68c3..bc232c334 100644
--- a/src/core/hle/service/nfc/common/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfc/common/amiibo_crypto.cpp
@@ -36,12 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
36 36
37 // Validate UUID 37 // Validate UUID
38 constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` 38 constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
39 if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) != 39 if ((CT ^ ntag_file.uuid.part1[0] ^ ntag_file.uuid.part1[1] ^ ntag_file.uuid.part1[2]) !=
40 ntag_file.uuid.uid[3]) { 40 ntag_file.uuid.crc_check1) {
41 return false; 41 return false;
42 } 42 }
43 if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^ 43 if ((ntag_file.uuid.part2[0] ^ ntag_file.uuid.part2[1] ^ ntag_file.uuid.part2[2] ^
44 ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) { 44 ntag_file.uuid.nintendo_id) != ntag_file.uuid_crc_check2) {
45 return false; 45 return false;
46 } 46 }
47 47
@@ -74,8 +74,9 @@ bool IsAmiiboValid(const NTAG215File& ntag_file) {
74NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { 74NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
75 NTAG215File encoded_data{}; 75 NTAG215File encoded_data{};
76 76
77 encoded_data.uid = nfc_data.uuid.uid; 77 encoded_data.uid = nfc_data.uuid;
78 encoded_data.nintendo_id = nfc_data.uuid.nintendo_id; 78 encoded_data.uid_crc_check2 = nfc_data.uuid_crc_check2;
79 encoded_data.internal_number = nfc_data.internal_number;
79 encoded_data.static_lock = nfc_data.static_lock; 80 encoded_data.static_lock = nfc_data.static_lock;
80 encoded_data.compability_container = nfc_data.compability_container; 81 encoded_data.compability_container = nfc_data.compability_container;
81 encoded_data.hmac_data = nfc_data.user_memory.hmac_data; 82 encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
@@ -94,7 +95,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
94 encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; 95 encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc;
95 encoded_data.application_area = nfc_data.user_memory.application_area; 96 encoded_data.application_area = nfc_data.user_memory.application_area;
96 encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; 97 encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
97 encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
98 encoded_data.model_info = nfc_data.user_memory.model_info; 98 encoded_data.model_info = nfc_data.user_memory.model_info;
99 encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; 99 encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
100 encoded_data.dynamic_lock = nfc_data.dynamic_lock; 100 encoded_data.dynamic_lock = nfc_data.dynamic_lock;
@@ -108,9 +108,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
108EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { 108EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
109 EncryptedNTAG215File nfc_data{}; 109 EncryptedNTAG215File nfc_data{};
110 110
111 nfc_data.uuid.uid = encoded_data.uid; 111 nfc_data.uuid = encoded_data.uid;
112 nfc_data.uuid.nintendo_id = encoded_data.nintendo_id; 112 nfc_data.uuid_crc_check2 = encoded_data.uid_crc_check2;
113 nfc_data.uuid.lock_bytes = encoded_data.lock_bytes; 113 nfc_data.internal_number = encoded_data.internal_number;
114 nfc_data.static_lock = encoded_data.static_lock; 114 nfc_data.static_lock = encoded_data.static_lock;
115 nfc_data.compability_container = encoded_data.compability_container; 115 nfc_data.compability_container = encoded_data.compability_container;
116 nfc_data.user_memory.hmac_data = encoded_data.hmac_data; 116 nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
@@ -139,23 +139,12 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
139 return nfc_data; 139 return nfc_data;
140} 140}
141 141
142u32 GetTagPassword(const TagUuid& uuid) {
143 // Verify that the generated password is correct
144 u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]);
145 password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8;
146 password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16;
147 password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24;
148 return password;
149}
150
151HashSeed GetSeed(const NTAG215File& data) { 142HashSeed GetSeed(const NTAG215File& data) {
152 HashSeed seed{ 143 HashSeed seed{
153 .magic = data.write_counter, 144 .magic = data.write_counter,
154 .padding = {}, 145 .padding = {},
155 .uid_1 = data.uid, 146 .uid_1 = data.uid,
156 .nintendo_id_1 = data.nintendo_id,
157 .uid_2 = data.uid, 147 .uid_2 = data.uid,
158 .nintendo_id_2 = data.nintendo_id,
159 .keygen_salt = data.keygen_salt, 148 .keygen_salt = data.keygen_salt,
160 }; 149 };
161 150
@@ -177,10 +166,11 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
177 output.insert(output.end(), key.magic_bytes.begin(), 166 output.insert(output.end(), key.magic_bytes.begin(),
178 key.magic_bytes.begin() + key.magic_length); 167 key.magic_bytes.begin() + key.magic_length);
179 168
180 output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end()); 169 std::array<u8, sizeof(NFP::TagUuid)> seed_uuid{};
181 output.emplace_back(seed.nintendo_id_1); 170 memcpy(seed_uuid.data(), &seed.uid_1, sizeof(NFP::TagUuid));
182 output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end()); 171 output.insert(output.end(), seed_uuid.begin(), seed_uuid.end());
183 output.emplace_back(seed.nintendo_id_2); 172 memcpy(seed_uuid.data(), &seed.uid_2, sizeof(NFP::TagUuid));
173 output.insert(output.end(), seed_uuid.begin(), seed_uuid.end());
184 174
185 for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { 175 for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
186 output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); 176 output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
@@ -264,8 +254,8 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou
264 254
265 // Copy the rest of the data directly 255 // Copy the rest of the data directly
266 out_data.uid = in_data.uid; 256 out_data.uid = in_data.uid;
267 out_data.nintendo_id = in_data.nintendo_id; 257 out_data.uid_crc_check2 = in_data.uid_crc_check2;
268 out_data.lock_bytes = in_data.lock_bytes; 258 out_data.internal_number = in_data.internal_number;
269 out_data.static_lock = in_data.static_lock; 259 out_data.static_lock = in_data.static_lock;
270 out_data.compability_container = in_data.compability_container; 260 out_data.compability_container = in_data.compability_container;
271 261
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.h b/src/core/hle/service/nfc/common/amiibo_crypto.h
index bf3044ed9..6a3e0841e 100644
--- a/src/core/hle/service/nfc/common/amiibo_crypto.h
+++ b/src/core/hle/service/nfc/common/amiibo_crypto.h
@@ -24,10 +24,8 @@ using DrgbOutput = std::array<u8, 0x20>;
24struct HashSeed { 24struct HashSeed {
25 u16_be magic; 25 u16_be magic;
26 std::array<u8, 0xE> padding; 26 std::array<u8, 0xE> padding;
27 NFC::UniqueSerialNumber uid_1; 27 TagUuid uid_1;
28 u8 nintendo_id_1; 28 TagUuid uid_2;
29 NFC::UniqueSerialNumber uid_2;
30 u8 nintendo_id_2;
31 std::array<u8, 0x20> keygen_salt; 29 std::array<u8, 0x20> keygen_salt;
32}; 30};
33static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); 31static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
@@ -69,9 +67,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
69/// Converts from encoded file format to encrypted file format 67/// Converts from encoded file format to encrypted file format
70EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); 68EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
71 69
72/// Returns password needed to allow write access to protected memory
73u32 GetTagPassword(const TagUuid& uuid);
74
75// Generates Seed needed for key derivation 70// Generates Seed needed for key derivation
76HashSeed GetSeed(const NTAG215File& data); 71HashSeed GetSeed(const NTAG215File& data);
77 72
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index b14f682b5..f4b180b06 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -242,34 +242,39 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
242 return ResultWrongDeviceState; 242 return ResultWrongDeviceState;
243 } 243 }
244 244
245 UniqueSerialNumber uuid = encrypted_tag_data.uuid.uid; 245 UniqueSerialNumber uuid{};
246 246 u8 uuid_length{};
247 // Generate random UUID to bypass amiibo load limits 247 NfcProtocol protocol{NfcProtocol::TypeA};
248 if (Settings::values.random_amiibo_id) { 248 TagType tag_type{TagType::Type2};
249 Common::TinyMT rng{};
250 rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
251 rng.GenerateRandomBytes(uuid.data(), sizeof(UniqueSerialNumber));
252 uuid[3] = 0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2];
253 }
254 249
255 if (is_mifare) { 250 if (is_mifare) {
256 tag_info = { 251 tag_type = TagType::Mifare;
257 .uuid = uuid, 252 uuid_length = sizeof(NFP::NtagTagUuid);
258 .uuid_extension = {}, 253 memcpy(uuid.data(), mifare_data.data(), uuid_length);
259 .uuid_length = static_cast<u8>(uuid.size()), 254 } else {
260 .protocol = NfcProtocol::TypeA, 255 tag_type = TagType::Type2;
261 .tag_type = TagType::Type4, 256 uuid_length = sizeof(NFP::NtagTagUuid);
257 NFP::NtagTagUuid nUuid{
258 .part1 = encrypted_tag_data.uuid.part1,
259 .part2 = encrypted_tag_data.uuid.part2,
260 .nintendo_id = encrypted_tag_data.uuid.nintendo_id,
262 }; 261 };
263 return ResultSuccess; 262 memcpy(uuid.data(), &nUuid, uuid_length);
263
264 // Generate random UUID to bypass amiibo load limits
265 if (Settings::values.random_amiibo_id) {
266 Common::TinyMT rng{};
267 rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
268 rng.GenerateRandomBytes(uuid.data(), uuid_length);
269 }
264 } 270 }
265 271
266 // Protocol and tag type may change here 272 // Protocol and tag type may change here
267 tag_info = { 273 tag_info = {
268 .uuid = uuid, 274 .uuid = uuid,
269 .uuid_extension = {}, 275 .uuid_length = uuid_length,
270 .uuid_length = static_cast<u8>(uuid.size()), 276 .protocol = protocol,
271 .protocol = NfcProtocol::TypeA, 277 .tag_type = tag_type,
272 .tag_type = TagType::Type2,
273 }; 278 };
274 279
275 return ResultSuccess; 280 return ResultSuccess;
@@ -277,8 +282,38 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
277 282
278Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, 283Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters,
279 std::span<MifareReadBlockData> read_block_data) const { 284 std::span<MifareReadBlockData> read_block_data) const {
285 if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
286 LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
287 if (device_state == DeviceState::TagRemoved) {
288 return ResultTagRemoved;
289 }
290 return ResultWrongDeviceState;
291 }
292
280 Result result = ResultSuccess; 293 Result result = ResultSuccess;
281 294
295 TagInfo tag_info{};
296 result = GetTagInfo(tag_info, true);
297
298 if (result.IsError()) {
299 return result;
300 }
301
302 if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) {
303 return ResultInvalidTagType;
304 }
305
306 if (parameters.size() == 0) {
307 return ResultInvalidArgument;
308 }
309
310 const auto unknown = parameters[0].sector_key.unknown;
311 for (std::size_t i = 0; i < parameters.size(); i++) {
312 if (unknown != parameters[i].sector_key.unknown) {
313 return ResultInvalidArgument;
314 }
315 }
316
282 for (std::size_t i = 0; i < parameters.size(); i++) { 317 for (std::size_t i = 0; i < parameters.size(); i++) {
283 result = ReadMifare(parameters[i], read_block_data[i]); 318 result = ReadMifare(parameters[i], read_block_data[i]);
284 if (result.IsError()) { 319 if (result.IsError()) {
@@ -293,17 +328,8 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
293 MifareReadBlockData& read_block_data) const { 328 MifareReadBlockData& read_block_data) const {
294 const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); 329 const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
295 read_block_data.sector_number = parameter.sector_number; 330 read_block_data.sector_number = parameter.sector_number;
296
297 if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
298 LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
299 if (device_state == DeviceState::TagRemoved) {
300 return ResultTagRemoved;
301 }
302 return ResultWrongDeviceState;
303 }
304
305 if (mifare_data.size() < sector_index + sizeof(DataBlock)) { 331 if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
306 return Mifare::ResultReadError; 332 return ResultMifareError288;
307 } 333 }
308 334
309 // TODO: Use parameter.sector_key to read encrypted data 335 // TODO: Use parameter.sector_key to read encrypted data
@@ -315,6 +341,28 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
315Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { 341Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) {
316 Result result = ResultSuccess; 342 Result result = ResultSuccess;
317 343
344 TagInfo tag_info{};
345 result = GetTagInfo(tag_info, true);
346
347 if (result.IsError()) {
348 return result;
349 }
350
351 if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) {
352 return ResultInvalidTagType;
353 }
354
355 if (parameters.size() == 0) {
356 return ResultInvalidArgument;
357 }
358
359 const auto unknown = parameters[0].sector_key.unknown;
360 for (std::size_t i = 0; i < parameters.size(); i++) {
361 if (unknown != parameters[i].sector_key.unknown) {
362 return ResultInvalidArgument;
363 }
364 }
365
318 for (std::size_t i = 0; i < parameters.size(); i++) { 366 for (std::size_t i = 0; i < parameters.size(); i++) {
319 result = WriteMifare(parameters[i]); 367 result = WriteMifare(parameters[i]);
320 if (result.IsError()) { 368 if (result.IsError()) {
@@ -324,7 +372,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet
324 372
325 if (!npad_device->WriteNfc(mifare_data)) { 373 if (!npad_device->WriteNfc(mifare_data)) {
326 LOG_ERROR(Service_NFP, "Error writing to file"); 374 LOG_ERROR(Service_NFP, "Error writing to file");
327 return Mifare::ResultReadError; 375 return ResultMifareError288;
328 } 376 }
329 377
330 return result; 378 return result;
@@ -342,7 +390,7 @@ Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) {
342 } 390 }
343 391
344 if (mifare_data.size() < sector_index + sizeof(DataBlock)) { 392 if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
345 return Mifare::ResultReadError; 393 return ResultMifareError288;
346 } 394 }
347 395
348 // TODO: Use parameter.sector_key to encrypt the data 396 // TODO: Use parameter.sector_key to encrypt the data
@@ -366,7 +414,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
366 414
367 if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { 415 if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
368 LOG_ERROR(Service_NFP, "Not an amiibo"); 416 LOG_ERROR(Service_NFP, "Not an amiibo");
369 return ResultNotAnAmiibo; 417 return ResultInvalidTagType;
370 } 418 }
371 419
372 // The loaded amiibo is not encrypted 420 // The loaded amiibo is not encrypted
@@ -381,14 +429,14 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
381 } 429 }
382 430
383 if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { 431 if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
384 bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess(); 432 bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
385 LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); 433 LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
386 return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData; 434 return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
387 } 435 }
388 436
389 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); 437 std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
390 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); 438 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
391 WriteBackupData(encrypted_tag_data.uuid.uid, data); 439 WriteBackupData(encrypted_tag_data.uuid, data);
392 440
393 device_state = DeviceState::TagMounted; 441 device_state = DeviceState::TagMounted;
394 mount_target = mount_target_; 442 mount_target = mount_target_;
@@ -492,7 +540,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
492 } 540 }
493 541
494 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); 542 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
495 WriteBackupData(encrypted_tag_data.uuid.uid, data); 543 WriteBackupData(encrypted_tag_data.uuid, data);
496 } 544 }
497 545
498 if (!npad_device->WriteNfc(data)) { 546 if (!npad_device->WriteNfc(data)) {
@@ -520,7 +568,7 @@ Result NfcDevice::Restore() {
520 return result; 568 return result;
521 } 569 }
522 570
523 result = ReadBackupData(tag_info.uuid, data); 571 result = ReadBackupData(tag_info.uuid, tag_info.uuid_length, data);
524 572
525 if (result.IsError()) { 573 if (result.IsError()) {
526 return result; 574 return result;
@@ -548,7 +596,7 @@ Result NfcDevice::Restore() {
548 } 596 }
549 597
550 if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) { 598 if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) {
551 return ResultNotAnAmiibo; 599 return ResultInvalidTagType;
552 } 600 }
553 601
554 if (!is_plain_amiibo) { 602 if (!is_plain_amiibo) {
@@ -1194,10 +1242,12 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) {
1194 return FlushWithBreak(break_type); 1242 return FlushWithBreak(break_type);
1195} 1243}
1196 1244
1197Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const { 1245Result NfcDevice::HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const {
1246 ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
1198 constexpr auto backup_dir = "backup"; 1247 constexpr auto backup_dir = "backup";
1199 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); 1248 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, "")); 1249 const auto file_name =
1250 fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));
1201 1251
1202 if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) { 1252 if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) {
1203 return ResultUnableToAccessBackupFile; 1253 return ResultUnableToAccessBackupFile;
@@ -1206,10 +1256,19 @@ Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const {
1206 return ResultSuccess; 1256 return ResultSuccess;
1207} 1257}
1208 1258
1209Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const { 1259Result NfcDevice::HasBackup(const NFP::TagUuid& tag_uid) const {
1260 UniqueSerialNumber uuid{};
1261 memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
1262 return HasBackup(uuid, sizeof(NFP::TagUuid));
1263}
1264
1265Result NfcDevice::ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
1266 std::span<u8> data) const {
1267 ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
1210 constexpr auto backup_dir = "backup"; 1268 constexpr auto backup_dir = "backup";
1211 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); 1269 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, "")); 1270 const auto file_name =
1271 fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));
1213 1272
1214 const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name, 1273 const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
1215 Common::FS::FileAccessMode::Read, 1274 Common::FS::FileAccessMode::Read,
@@ -1228,12 +1287,21 @@ Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u
1228 return ResultSuccess; 1287 return ResultSuccess;
1229} 1288}
1230 1289
1231Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) { 1290Result NfcDevice::ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const {
1291 UniqueSerialNumber uuid{};
1292 memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
1293 return ReadBackupData(uuid, sizeof(NFP::TagUuid), data);
1294}
1295
1296Result NfcDevice::WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
1297 std::span<const u8> data) {
1298 ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
1232 constexpr auto backup_dir = "backup"; 1299 constexpr auto backup_dir = "backup";
1233 const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); 1300 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, "")); 1301 const auto file_name =
1302 fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));
1235 1303
1236 if (HasBackup(uid).IsError()) { 1304 if (HasBackup(uid, uuid_size).IsError()) {
1237 if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) { 1305 if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) {
1238 return ResultBackupPathAlreadyExist; 1306 return ResultBackupPathAlreadyExist;
1239 } 1307 }
@@ -1260,6 +1328,12 @@ Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<
1260 return ResultSuccess; 1328 return ResultSuccess;
1261} 1329}
1262 1330
1331Result NfcDevice::WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data) {
1332 UniqueSerialNumber uuid{};
1333 memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
1334 return WriteBackupData(uuid, sizeof(NFP::TagUuid), data);
1335}
1336
1263Result NfcDevice::WriteNtf(std::span<const u8> data) { 1337Result NfcDevice::WriteNtf(std::span<const u8> data) {
1264 if (device_state != DeviceState::TagMounted) { 1338 if (device_state != DeviceState::TagMounted) {
1265 LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); 1339 LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 6f049b687..7560210d6 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -86,9 +86,14 @@ 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 HasBackup(const NFC::UniqueSerialNumber& uid) const; 89 Result HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const;
90 Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const; 90 Result HasBackup(const NFP::TagUuid& tag_uid) const;
91 Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data); 91 Result ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
92 std::span<u8> data) const;
93 Result ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const;
94 Result WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
95 std::span<const u8> data);
96 Result WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data);
92 Result WriteNtf(std::span<const u8> data); 97 Result WriteNtf(std::span<const u8> data);
93 98
94 u64 GetHandle() const; 99 u64 GetHandle() const;
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp
index cffd602df..b0456508e 100644
--- a/src/core/hle/service/nfc/common/device_manager.cpp
+++ b/src/core/hle/service/nfc/common/device_manager.cpp
@@ -550,7 +550,7 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
550 } 550 }
551 551
552 if (result.IsSuccess()) { 552 if (result.IsSuccess()) {
553 result = device->ReadBackupData(tag_info.uuid, data); 553 result = device->ReadBackupData(tag_info.uuid, tag_info.uuid_length, data);
554 result = VerifyDeviceResult(device, result); 554 result = VerifyDeviceResult(device, result);
555 } 555 }
556 556
@@ -569,7 +569,7 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
569 } 569 }
570 570
571 if (result.IsSuccess()) { 571 if (result.IsSuccess()) {
572 result = device->WriteBackupData(tag_info.uuid, data); 572 result = device->WriteBackupData(tag_info.uuid, tag_info.uuid_length, data);
573 result = VerifyDeviceResult(device, result); 573 result = VerifyDeviceResult(device, result);
574 } 574 }
575 575
diff --git a/src/core/hle/service/nfc/mifare_result.h b/src/core/hle/service/nfc/mifare_result.h
index 4b60048a5..16a9171e6 100644
--- a/src/core/hle/service/nfc/mifare_result.h
+++ b/src/core/hle/service/nfc/mifare_result.h
@@ -12,6 +12,6 @@ constexpr Result ResultInvalidArgument(ErrorModule::NFCMifare, 65);
12constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73); 12constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73);
13constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80); 13constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80);
14constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97); 14constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97);
15constexpr Result ResultReadError(ErrorModule::NFCMifare, 288); 15constexpr Result ResultNotAMifare(ErrorModule::NFCMifare, 288);
16 16
17} // namespace Service::NFC::Mifare 17} // namespace Service::NFC::Mifare
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp
index 198d0f2b9..130fb7f78 100644
--- a/src/core/hle/service/nfc/nfc_interface.cpp
+++ b/src/core/hle/service/nfc/nfc_interface.cpp
@@ -142,9 +142,13 @@ void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
142void NfcInterface::StartDetection(HLERequestContext& ctx) { 142void NfcInterface::StartDetection(HLERequestContext& ctx) {
143 IPC::RequestParser rp{ctx}; 143 IPC::RequestParser rp{ctx};
144 const auto device_handle{rp.Pop<u64>()}; 144 const auto device_handle{rp.Pop<u64>()};
145 const auto tag_protocol{rp.PopEnum<NfcProtocol>()}; 145 auto tag_protocol{NfcProtocol::All};
146 LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); 146
147 if (backend_type == BackendType::Nfc) {
148 tag_protocol = rp.PopEnum<NfcProtocol>();
149 }
147 150
151 LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol);
148 auto result = GetManager()->StartDetection(device_handle, tag_protocol); 152 auto result = GetManager()->StartDetection(device_handle, tag_protocol);
149 result = TranslateResultToServiceError(result); 153 result = TranslateResultToServiceError(result);
150 154
@@ -355,7 +359,7 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
355 if (result == ResultApplicationAreaExist) { 359 if (result == ResultApplicationAreaExist) {
356 return NFP::ResultApplicationAreaExist; 360 return NFP::ResultApplicationAreaExist;
357 } 361 }
358 if (result == ResultNotAnAmiibo) { 362 if (result == ResultInvalidTagType) {
359 return NFP::ResultNotAnAmiibo; 363 return NFP::ResultNotAnAmiibo;
360 } 364 }
361 if (result == ResultUnableToAccessBackupFile) { 365 if (result == ResultUnableToAccessBackupFile) {
@@ -381,6 +385,9 @@ Result NfcInterface::TranslateResultToMifare(Result result) const {
381 if (result == ResultTagRemoved) { 385 if (result == ResultTagRemoved) {
382 return Mifare::ResultTagRemoved; 386 return Mifare::ResultTagRemoved;
383 } 387 }
388 if (result == ResultInvalidTagType) {
389 return Mifare::ResultNotAMifare;
390 }
384 LOG_WARNING(Service_NFC, "Result conversion not handled"); 391 LOG_WARNING(Service_NFC, "Result conversion not handled");
385 return result; 392 return result;
386} 393}
diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h
index 59a808740..715c0e80c 100644
--- a/src/core/hle/service/nfc/nfc_result.h
+++ b/src/core/hle/service/nfc/nfc_result.h
@@ -24,7 +24,8 @@ constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136);
24constexpr Result ResultCorruptedData(ErrorModule::NFC, 144); 24constexpr Result ResultCorruptedData(ErrorModule::NFC, 144);
25constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152); 25constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152);
26constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168); 26constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168);
27constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178); 27constexpr Result ResultInvalidTagType(ErrorModule::NFC, 178);
28constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216); 28constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216);
29constexpr Result ResultMifareError288(ErrorModule::NFC, 288);
29 30
30} // namespace Service::NFC 31} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_types.h b/src/core/hle/service/nfc/nfc_types.h
index c7ebd1fdb..68e724442 100644
--- a/src/core/hle/service/nfc/nfc_types.h
+++ b/src/core/hle/service/nfc/nfc_types.h
@@ -35,32 +35,35 @@ enum class State : u32 {
35 35
36// This is nn::nfc::TagType 36// This is nn::nfc::TagType
37enum class TagType : u32 { 37enum class TagType : u32 {
38 None, 38 None = 0,
39 Type1, // ISO14443A RW 96-2k bytes 106kbit/s 39 Type1 = 1U << 0, // ISO14443A RW. Topaz
40 Type2, // ISO14443A RW/RO 540 bytes 106kbit/s 40 Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN
41 Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s 41 Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa
42 Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s 42 Type4A = 1U << 3, // ISO14443A RW/RO. DESFire
43 Type5, // ISO15693 RW/RO 540 bytes 106kbit/s 43 Type4B = 1U << 4, // ISO14443B RW/RO. DESFire
44 Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV
45 Mifare = 1U << 6, // Mifare classic. Skylanders
46 All = 0xFFFFFFFF,
44}; 47};
45 48
46enum class PackedTagType : u8 { 49enum class PackedTagType : u8 {
47 None, 50 None = 0,
48 Type1, // ISO14443A RW 96-2k bytes 106kbit/s 51 Type1 = 1U << 0, // ISO14443A RW. Topaz
49 Type2, // ISO14443A RW/RO 540 bytes 106kbit/s 52 Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN
50 Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s 53 Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa
51 Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s 54 Type4A = 1U << 3, // ISO14443A RW/RO. DESFire
52 Type5, // ISO15693 RW/RO 540 bytes 106kbit/s 55 Type4B = 1U << 4, // ISO14443B RW/RO. DESFire
56 Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV
57 Mifare = 1U << 6, // Mifare classic. Skylanders
58 All = 0xFF,
53}; 59};
54 60
55// This is nn::nfc::NfcProtocol 61// This is nn::nfc::NfcProtocol
56// Verify this enum. It might be completely wrong default protocol is 0x48
57enum class NfcProtocol : u32 { 62enum class NfcProtocol : u32 {
58 None, 63 None,
59 TypeA = 1U << 0, // ISO14443A 64 TypeA = 1U << 0, // ISO14443A
60 TypeB = 1U << 1, // ISO14443B 65 TypeB = 1U << 1, // ISO14443B
61 TypeF = 1U << 2, // Sony FeliCa 66 TypeF = 1U << 2, // Sony FeliCa
62 Unknown1 = 1U << 3,
63 Unknown2 = 1U << 5,
64 All = 0xFFFFFFFFU, 67 All = 0xFFFFFFFFU,
65}; 68};
66 69
@@ -69,8 +72,7 @@ enum class TestWaveType : u32 {
69 Unknown, 72 Unknown,
70}; 73};
71 74
72using UniqueSerialNumber = std::array<u8, 7>; 75using UniqueSerialNumber = std::array<u8, 10>;
73using UniqueSerialNumberExtension = std::array<u8, 3>;
74 76
75// This is nn::nfc::DeviceHandle 77// This is nn::nfc::DeviceHandle
76using DeviceHandle = u64; 78using DeviceHandle = u64;
@@ -78,7 +80,6 @@ using DeviceHandle = u64;
78// This is nn::nfc::TagInfo 80// This is nn::nfc::TagInfo
79struct TagInfo { 81struct TagInfo {
80 UniqueSerialNumber uuid; 82 UniqueSerialNumber uuid;
81 UniqueSerialNumberExtension uuid_extension;
82 u8 uuid_length; 83 u8 uuid_length;
83 INSERT_PADDING_BYTES(0x15); 84 INSERT_PADDING_BYTES(0x15);
84 NfcProtocol protocol; 85 NfcProtocol protocol;
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index 7d36d5ee6..aed12a7f8 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -85,7 +85,7 @@ enum class CabinetMode : u8 {
85 StartFormatter, 85 StartFormatter,
86}; 86};
87 87
88using LockBytes = std::array<u8, 2>; 88using UuidPart = std::array<u8, 3>;
89using HashData = std::array<u8, 0x20>; 89using HashData = std::array<u8, 0x20>;
90using ApplicationArea = std::array<u8, 0xD8>; 90using ApplicationArea = std::array<u8, 0xD8>;
91using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; 91using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
@@ -93,12 +93,20 @@ using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
93// This is nn::nfp::TagInfo 93// This is nn::nfp::TagInfo
94using TagInfo = NFC::TagInfo; 94using TagInfo = NFC::TagInfo;
95 95
96struct NtagTagUuid {
97 UuidPart part1;
98 UuidPart part2;
99 u8 nintendo_id;
100};
101static_assert(sizeof(NtagTagUuid) == 7, "NtagTagUuid is an invalid size");
102
96struct TagUuid { 103struct TagUuid {
97 NFC::UniqueSerialNumber uid; 104 UuidPart part1;
105 u8 crc_check1;
106 UuidPart part2;
98 u8 nintendo_id; 107 u8 nintendo_id;
99 LockBytes lock_bytes;
100}; 108};
101static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size"); 109static_assert(sizeof(TagUuid) == 8, "TagUuid is an invalid size");
102 110
103struct WriteDate { 111struct WriteDate {
104 u16 year; 112 u16 year;
@@ -231,7 +239,8 @@ struct EncryptedAmiiboFile {
231static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); 239static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
232 240
233struct NTAG215File { 241struct NTAG215File {
234 LockBytes lock_bytes; // Tag UUID 242 u8 uid_crc_check2;
243 u8 internal_number;
235 u16 static_lock; // Set defined pages as read only 244 u16 static_lock; // Set defined pages as read only
236 u32 compability_container; // Defines available memory 245 u32 compability_container; // Defines available memory
237 HashData hmac_data; // Hash 246 HashData hmac_data; // Hash
@@ -250,8 +259,7 @@ struct NTAG215File {
250 u32_be register_info_crc; 259 u32_be register_info_crc;
251 ApplicationArea application_area; // Encrypted Game data 260 ApplicationArea application_area; // Encrypted Game data
252 HashData hmac_tag; // Hash 261 HashData hmac_tag; // Hash
253 NFC::UniqueSerialNumber uid; // Unique serial number 262 TagUuid uid;
254 u8 nintendo_id; // Tag UUID
255 AmiiboModelInfo model_info; 263 AmiiboModelInfo model_info;
256 HashData keygen_salt; // Salt 264 HashData keygen_salt; // Salt
257 u32 dynamic_lock; // Dynamic lock 265 u32 dynamic_lock; // Dynamic lock
@@ -264,7 +272,9 @@ static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be tr
264#pragma pack() 272#pragma pack()
265 273
266struct EncryptedNTAG215File { 274struct EncryptedNTAG215File {
267 TagUuid uuid; // Unique serial number 275 TagUuid uuid;
276 u8 uuid_crc_check2;
277 u8 internal_number;
268 u16 static_lock; // Set defined pages as read only 278 u16 static_lock; // Set defined pages as read only
269 u32 compability_container; // Defines available memory 279 u32 compability_container; // Defines available memory
270 EncryptedAmiiboFile user_memory; // Writable data 280 EncryptedAmiiboFile user_memory; // Writable data
diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp
index e1728c06d..205371a26 100644
--- a/src/core/hle/service/time/time_zone_manager.cpp
+++ b/src/core/hle/service/time/time_zone_manager.cpp
@@ -849,8 +849,9 @@ static Result CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal&
849static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, 849static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
850 CalendarTimeInternal& calendar_time, 850 CalendarTimeInternal& calendar_time,
851 CalendarAdditionalInfo& calendar_additional_info) { 851 CalendarAdditionalInfo& calendar_additional_info) {
852 if ((rules.go_ahead && time < rules.ats[0]) || 852 ASSERT(rules.go_ahead ? rules.time_count > 0 : true);
853 (rules.go_back && time > rules.ats[rules.time_count - 1])) { 853 if ((rules.go_back && time < rules.ats[0]) ||
854 (rules.go_ahead && time > rules.ats[rules.time_count - 1])) {
854 s64 seconds{}; 855 s64 seconds{};
855 if (time < rules.ats[0]) { 856 if (time < rules.ats[0]) {
856 seconds = rules.ats[0] - time; 857 seconds = rules.ats[0] - time;
@@ -910,9 +911,13 @@ static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
910 911
911 calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst; 912 calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst;
912 const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]}; 913 const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]};
913 for (int index{}; time_zone[index] != '\0'; ++index) { 914 u32 index;
915 for (index = 0; time_zone[index] != '\0' && time_zone[index] != ',' &&
916 index < calendar_additional_info.timezone_name.size() - 1;
917 ++index) {
914 calendar_additional_info.timezone_name[index] = time_zone[index]; 918 calendar_additional_info.timezone_name[index] = time_zone[index];
915 } 919 }
920 calendar_additional_info.timezone_name[index] = '\0';
916 return ResultSuccess; 921 return ResultSuccess;
917} 922}
918 923
diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp
index e8273e152..8171c82a5 100644
--- a/src/core/hle/service/time/time_zone_service.cpp
+++ b/src/core/hle/service/time/time_zone_service.cpp
@@ -112,20 +112,14 @@ void ITimeZoneService::LoadTimeZoneRule(HLERequestContext& ctx) {
112 LOG_DEBUG(Service_Time, "called, location_name={}", location_name); 112 LOG_DEBUG(Service_Time, "called, location_name={}", location_name);
113 113
114 TimeZone::TimeZoneRule time_zone_rule{}; 114 TimeZone::TimeZoneRule time_zone_rule{};
115 if (const Result result{ 115 const Result result{time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)};
116 time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)};
117 result != ResultSuccess) {
118 IPC::ResponseBuilder rb{ctx, 2};
119 rb.Push(result);
120 return;
121 }
122 116
123 std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule)); 117 std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule));
124 std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule)); 118 std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule));
125 ctx.WriteBuffer(time_zone_rule_outbuffer); 119 ctx.WriteBuffer(time_zone_rule_outbuffer);
126 120
127 IPC::ResponseBuilder rb{ctx, 2}; 121 IPC::ResponseBuilder rb{ctx, 2};
128 rb.Push(ResultSuccess); 122 rb.Push(result);
129} 123}
130 124
131void ITimeZoneService::ToCalendarTime(HLERequestContext& ctx) { 125void ITimeZoneService::ToCalendarTime(HLERequestContext& ctx) {
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
index f8bafe553..6435b8af8 100644
--- a/src/input_common/drivers/virtual_amiibo.cpp
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -82,6 +82,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
82 switch (nfc_file.GetSize()) { 82 switch (nfc_file.GetSize()) {
83 case AmiiboSize: 83 case AmiiboSize:
84 case AmiiboSizeWithoutPassword: 84 case AmiiboSizeWithoutPassword:
85 case AmiiboSizeWithSignature:
85 data.resize(AmiiboSize); 86 data.resize(AmiiboSize);
86 if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { 87 if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) {
87 return Info::NotAnAmiibo; 88 return Info::NotAnAmiibo;
@@ -109,6 +110,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) {
109 switch (data.size_bytes()) { 110 switch (data.size_bytes()) {
110 case AmiiboSize: 111 case AmiiboSize:
111 case AmiiboSizeWithoutPassword: 112 case AmiiboSizeWithoutPassword:
113 case AmiiboSizeWithSignature:
112 nfc_data.resize(AmiiboSize); 114 nfc_data.resize(AmiiboSize);
113 break; 115 break;
114 case MifareSize: 116 case MifareSize:
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
index 34e97cd91..09ca09e68 100644
--- a/src/input_common/drivers/virtual_amiibo.h
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -57,6 +57,7 @@ public:
57private: 57private:
58 static constexpr std::size_t AmiiboSize = 0x21C; 58 static constexpr std::size_t AmiiboSize = 0x21C;
59 static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; 59 static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8;
60 static constexpr std::size_t AmiiboSizeWithSignature = AmiiboSize + 0x20;
60 static constexpr std::size_t MifareSize = 0x400; 61 static constexpr std::size_t MifareSize = 0x400;
61 62
62 std::string file_path{}; 63 std::string file_path{};
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 251a4a880..45977d578 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -715,13 +715,19 @@ void BufferCache<P>::BindHostIndexBuffer() {
715 715
716template <class P> 716template <class P>
717void BufferCache<P>::BindHostVertexBuffers() { 717void BufferCache<P>::BindHostVertexBuffers() {
718 HostBindings host_bindings; 718 HostBindings<typename P::Buffer> host_bindings;
719 bool any_valid{false}; 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);
722 if (!flags[Dirty::VertexBuffer0 + index]) { 726 if (!flags[Dirty::VertexBuffer0 + index]) {
723 continue; 727 continue;
724 } 728 }
729 flags[Dirty::VertexBuffer0 + index] = false;
730
725 host_bindings.min_index = std::min(host_bindings.min_index, index); 731 host_bindings.min_index = std::min(host_bindings.min_index, index);
726 host_bindings.max_index = std::max(host_bindings.max_index, index); 732 host_bindings.max_index = std::max(host_bindings.max_index, index);
727 any_valid = true; 733 any_valid = true;
@@ -735,13 +741,10 @@ void BufferCache<P>::BindHostVertexBuffers() {
735 const Binding& binding = channel_state->vertex_buffers[index]; 741 const Binding& binding = channel_state->vertex_buffers[index];
736 Buffer& buffer = slot_buffers[binding.buffer_id]; 742 Buffer& buffer = slot_buffers[binding.buffer_id];
737 743
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; 744 const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
742 const u32 offset = buffer.Offset(binding.cpu_addr); 745 const u32 offset = buffer.Offset(binding.cpu_addr);
743 746
744 host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer)); 747 host_bindings.buffers.push_back(&buffer);
745 host_bindings.offsets.push_back(offset); 748 host_bindings.offsets.push_back(offset);
746 host_bindings.sizes.push_back(binding.size); 749 host_bindings.sizes.push_back(binding.size);
747 host_bindings.strides.push_back(stride); 750 host_bindings.strides.push_back(stride);
@@ -900,7 +903,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
900 if (maxwell3d->regs.transform_feedback_enabled == 0) { 903 if (maxwell3d->regs.transform_feedback_enabled == 0) {
901 return; 904 return;
902 } 905 }
903 HostBindings host_bindings; 906 HostBindings<typename P::Buffer> host_bindings;
904 for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { 907 for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
905 const Binding& binding = channel_state->transform_feedback_buffers[index]; 908 const Binding& binding = channel_state->transform_feedback_buffers[index];
906 if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 && 909 if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
@@ -913,7 +916,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
913 SynchronizeBuffer(buffer, binding.cpu_addr, size); 916 SynchronizeBuffer(buffer, binding.cpu_addr, size);
914 917
915 const u32 offset = buffer.Offset(binding.cpu_addr); 918 const u32 offset = buffer.Offset(binding.cpu_addr);
916 host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer)); 919 host_bindings.buffers.push_back(&buffer);
917 host_bindings.offsets.push_back(offset); 920 host_bindings.offsets.push_back(offset);
918 host_bindings.sizes.push_back(binding.size); 921 host_bindings.sizes.push_back(binding.size);
919 } 922 }
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index cf359e241..63a120f7a 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -105,8 +105,9 @@ static constexpr Binding NULL_BINDING{
105 .buffer_id = NULL_BUFFER_ID, 105 .buffer_id = NULL_BUFFER_ID,
106}; 106};
107 107
108template <typename Buffer>
108struct HostBindings { 109struct HostBindings {
109 boost::container::small_vector<void*, NUM_VERTEX_BUFFERS> buffers; 110 boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
110 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets; 111 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> sizes;
112 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides; 113 boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp
index 0e94c521a..f34090791 100644
--- a/src/video_core/engines/draw_manager.cpp
+++ b/src/video_core/engines/draw_manager.cpp
@@ -1,6 +1,7 @@
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 "common/settings.h"
4#include "video_core/dirty_flags.h" 5#include "video_core/dirty_flags.h"
5#include "video_core/engines/draw_manager.h" 6#include "video_core/engines/draw_manager.h"
6#include "video_core/rasterizer_interface.h" 7#include "video_core/rasterizer_interface.h"
@@ -195,8 +196,12 @@ void DrawManager::DrawTexture() {
195 if (lower_left) { 196 if (lower_left) {
196 draw_texture_state.dst_y0 -= dst_height; 197 draw_texture_state.dst_y0 -= dst_height;
197 } 198 }
198 draw_texture_state.dst_x1 = draw_texture_state.dst_x0 + dst_width; 199 draw_texture_state.dst_x1 =
199 draw_texture_state.dst_y1 = draw_texture_state.dst_y0 + dst_height; 200 draw_texture_state.dst_x0 +
201 static_cast<f32>(Settings::values.resolution_info.ScaleUp(static_cast<u32>(dst_width)));
202 draw_texture_state.dst_y1 =
203 draw_texture_state.dst_y0 +
204 static_cast<f32>(Settings::values.resolution_info.ScaleUp(static_cast<u32>(dst_height)));
200 draw_texture_state.src_x0 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f; 205 draw_texture_state.src_x0 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f;
201 draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f; 206 draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f;
202 draw_texture_state.src_x1 = 207 draw_texture_state.src_x1 =
@@ -207,7 +212,6 @@ void DrawManager::DrawTexture() {
207 draw_texture_state.src_y0; 212 draw_texture_state.src_y0;
208 draw_texture_state.src_sampler = regs.draw_texture.src_sampler; 213 draw_texture_state.src_sampler = regs.draw_texture.src_sampler;
209 draw_texture_state.src_texture = regs.draw_texture.src_texture; 214 draw_texture_state.src_texture = regs.draw_texture.src_texture;
210
211 maxwell3d->rasterizer->DrawTexture(); 215 maxwell3d->rasterizer->DrawTexture();
212} 216}
213 217
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 7b2cde7a7..b2f7e160a 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -111,7 +111,7 @@ GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cp
111 [[maybe_unused]] const auto current_entry_type = GetEntry<false>(current_gpu_addr); 111 [[maybe_unused]] const auto current_entry_type = GetEntry<false>(current_gpu_addr);
112 SetEntry<false>(current_gpu_addr, entry_type); 112 SetEntry<false>(current_gpu_addr, entry_type);
113 if (current_entry_type != entry_type) { 113 if (current_entry_type != entry_type) {
114 rasterizer->ModifyGPUMemory(unique_identifier, gpu_addr, page_size); 114 rasterizer->ModifyGPUMemory(unique_identifier, current_gpu_addr, page_size);
115 } 115 }
116 if constexpr (entry_type == EntryType::Mapped) { 116 if constexpr (entry_type == EntryType::Mapped) {
117 const VAddr current_cpu_addr = cpu_addr + offset; 117 const VAddr current_cpu_addr = cpu_addr + offset;
@@ -134,7 +134,7 @@ GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr
134 [[maybe_unused]] const auto current_entry_type = GetEntry<true>(current_gpu_addr); 134 [[maybe_unused]] const auto current_entry_type = GetEntry<true>(current_gpu_addr);
135 SetEntry<true>(current_gpu_addr, entry_type); 135 SetEntry<true>(current_gpu_addr, entry_type);
136 if (current_entry_type != entry_type) { 136 if (current_entry_type != entry_type) {
137 rasterizer->ModifyGPUMemory(unique_identifier, gpu_addr, big_page_size); 137 rasterizer->ModifyGPUMemory(unique_identifier, current_gpu_addr, big_page_size);
138 } 138 }
139 if constexpr (entry_type == EntryType::Mapped) { 139 if constexpr (entry_type == EntryType::Mapped) {
140 const VAddr current_cpu_addr = cpu_addr + offset; 140 const VAddr current_cpu_addr = cpu_addr + offset;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 0cc546a3a..38d553d3c 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -232,12 +232,12 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset,
232 } 232 }
233} 233}
234 234
235void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { 235void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
236 for (u32 index = 0; index < bindings.buffers.size(); index++) { 236 for (u32 index = 0; index < bindings.buffers.size(); ++index) {
237 BindVertexBuffer( 237 BindVertexBuffer(bindings.min_index + index, *bindings.buffers[index],
238 bindings.min_index + index, *reinterpret_cast<Buffer*>(bindings.buffers[index]), 238 static_cast<u32>(bindings.offsets[index]),
239 static_cast<u32>(bindings.offsets[index]), static_cast<u32>(bindings.sizes[index]), 239 static_cast<u32>(bindings.sizes[index]),
240 static_cast<u32>(bindings.strides[index])); 240 static_cast<u32>(bindings.strides[index]));
241 } 241 }
242} 242}
243 243
@@ -329,10 +329,9 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer,
329 static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); 329 static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
330} 330}
331 331
332void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) { 332void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
333 for (u32 index = 0; index < bindings.buffers.size(); index++) { 333 for (u32 index = 0; index < bindings.buffers.size(); ++index) {
334 glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, 334 glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, bindings.buffers[index]->Handle(),
335 reinterpret_cast<Buffer*>(bindings.buffers[index])->Handle(),
336 static_cast<GLintptr>(bindings.offsets[index]), 335 static_cast<GLintptr>(bindings.offsets[index]),
337 static_cast<GLsizeiptr>(bindings.sizes[index])); 336 static_cast<GLsizeiptr>(bindings.sizes[index]));
338 } 337 }
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index e4e000284..41b746f3b 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -87,7 +87,8 @@ public:
87 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); 87 void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
88 88
89 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); 90
91 void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
91 92
92 void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); 93 void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
93 94
@@ -100,7 +101,8 @@ public:
100 bool is_written); 101 bool is_written);
101 102
102 void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); 103 void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size);
103 void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); 104
105 void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
104 106
105 void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, 107 void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
106 VideoCore::Surface::PixelFormat format); 108 VideoCore::Surface::PixelFormat format);
diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
index 1a0cea9b7..3151c0db8 100644
--- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
@@ -87,7 +87,8 @@ void ComputePipeline::Configure() {
87 texture_cache.SynchronizeComputeDescriptors(); 87 texture_cache.SynchronizeComputeDescriptors();
88 88
89 boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; 89 boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
90 std::array<GLuint, MAX_TEXTURES> samplers; 90 boost::container::static_vector<VideoCommon::SamplerId, MAX_TEXTURES> samplers;
91 std::array<GLuint, MAX_TEXTURES> gl_samplers;
91 std::array<GLuint, MAX_TEXTURES> textures; 92 std::array<GLuint, MAX_TEXTURES> textures;
92 std::array<GLuint, MAX_IMAGES> images; 93 std::array<GLuint, MAX_IMAGES> images;
93 GLsizei sampler_binding{}; 94 GLsizei sampler_binding{};
@@ -131,7 +132,6 @@ void ComputePipeline::Configure() {
131 for (u32 index = 0; index < desc.count; ++index) { 132 for (u32 index = 0; index < desc.count; ++index) {
132 const auto handle{read_handle(desc, index)}; 133 const auto handle{read_handle(desc, index)};
133 views.push_back({handle.first}); 134 views.push_back({handle.first});
134 samplers[sampler_binding++] = 0;
135 } 135 }
136 } 136 }
137 for (const auto& desc : info.image_buffer_descriptors) { 137 for (const auto& desc : info.image_buffer_descriptors) {
@@ -142,8 +142,8 @@ void ComputePipeline::Configure() {
142 const auto handle{read_handle(desc, index)}; 142 const auto handle{read_handle(desc, index)};
143 views.push_back({handle.first}); 143 views.push_back({handle.first});
144 144
145 Sampler* const sampler = texture_cache.GetComputeSampler(handle.second); 145 VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second);
146 samplers[sampler_binding++] = sampler->Handle(); 146 samplers.push_back(sampler);
147 } 147 }
148 } 148 }
149 for (const auto& desc : info.image_descriptors) { 149 for (const auto& desc : info.image_descriptors) {
@@ -186,10 +186,17 @@ void ComputePipeline::Configure() {
186 186
187 const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers + 187 const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers +
188 num_image_buffers}; 188 num_image_buffers};
189 const VideoCommon::SamplerId* samplers_it{samplers.data()};
189 texture_binding += num_texture_buffers; 190 texture_binding += num_texture_buffers;
190 image_binding += num_image_buffers; 191 image_binding += num_image_buffers;
191 192
192 u32 texture_scaling_mask{}; 193 u32 texture_scaling_mask{};
194
195 for (const auto& desc : info.texture_buffer_descriptors) {
196 for (u32 index = 0; index < desc.count; ++index) {
197 gl_samplers[sampler_binding++] = 0;
198 }
199 }
193 for (const auto& desc : info.texture_descriptors) { 200 for (const auto& desc : info.texture_descriptors) {
194 for (u32 index = 0; index < desc.count; ++index) { 201 for (u32 index = 0; index < desc.count; ++index) {
195 ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; 202 ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
@@ -198,6 +205,12 @@ void ComputePipeline::Configure() {
198 texture_scaling_mask |= 1u << texture_binding; 205 texture_scaling_mask |= 1u << texture_binding;
199 } 206 }
200 ++texture_binding; 207 ++texture_binding;
208
209 const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))};
210 const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
211 !image_view.SupportsAnisotropy()};
212 gl_samplers[sampler_binding++] =
213 use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle();
201 } 214 }
202 } 215 }
203 u32 image_scaling_mask{}; 216 u32 image_scaling_mask{};
@@ -228,7 +241,7 @@ void ComputePipeline::Configure() {
228 if (texture_binding != 0) { 241 if (texture_binding != 0) {
229 ASSERT(texture_binding == sampler_binding); 242 ASSERT(texture_binding == sampler_binding);
230 glBindTextures(0, texture_binding, textures.data()); 243 glBindTextures(0, texture_binding, textures.data());
231 glBindSamplers(0, sampler_binding, samplers.data()); 244 glBindSamplers(0, sampler_binding, gl_samplers.data());
232 } 245 }
233 if (image_binding != 0) { 246 if (image_binding != 0) {
234 glBindImageTextures(0, image_binding, images.data()); 247 glBindImageTextures(0, image_binding, images.data());
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index 89000d6e0..c58f760b8 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -275,9 +275,9 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
275template <typename Spec> 275template <typename Spec>
276void GraphicsPipeline::ConfigureImpl(bool is_indexed) { 276void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
277 std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; 277 std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
278 std::array<GLuint, MAX_TEXTURES> samplers; 278 std::array<VideoCommon::SamplerId, MAX_TEXTURES> samplers;
279 size_t views_index{}; 279 size_t views_index{};
280 GLsizei sampler_binding{}; 280 size_t samplers_index{};
281 281
282 texture_cache.SynchronizeGraphicsDescriptors(); 282 texture_cache.SynchronizeGraphicsDescriptors();
283 283
@@ -337,7 +337,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
337 for (u32 index = 0; index < desc.count; ++index) { 337 for (u32 index = 0; index < desc.count; ++index) {
338 const auto handle{read_handle(desc, index)}; 338 const auto handle{read_handle(desc, index)};
339 views[views_index++] = {handle.first}; 339 views[views_index++] = {handle.first};
340 samplers[sampler_binding++] = 0;
341 } 340 }
342 } 341 }
343 } 342 }
@@ -351,8 +350,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
351 const auto handle{read_handle(desc, index)}; 350 const auto handle{read_handle(desc, index)};
352 views[views_index++] = {handle.first}; 351 views[views_index++] = {handle.first};
353 352
354 Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)}; 353 VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)};
355 samplers[sampler_binding++] = sampler->Handle(); 354 samplers[samplers_index++] = sampler;
356 } 355 }
357 } 356 }
358 if constexpr (Spec::has_images) { 357 if constexpr (Spec::has_images) {
@@ -445,10 +444,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
445 program_manager.BindSourcePrograms(source_programs); 444 program_manager.BindSourcePrograms(source_programs);
446 } 445 }
447 const VideoCommon::ImageViewInOut* views_it{views.data()}; 446 const VideoCommon::ImageViewInOut* views_it{views.data()};
447 const VideoCommon::SamplerId* samplers_it{samplers.data()};
448 GLsizei texture_binding = 0; 448 GLsizei texture_binding = 0;
449 GLsizei image_binding = 0; 449 GLsizei image_binding = 0;
450 GLsizei sampler_binding{};
450 std::array<GLuint, MAX_TEXTURES> textures; 451 std::array<GLuint, MAX_TEXTURES> textures;
451 std::array<GLuint, MAX_IMAGES> images; 452 std::array<GLuint, MAX_IMAGES> images;
453 std::array<GLuint, MAX_TEXTURES> gl_samplers;
452 const auto prepare_stage{[&](size_t stage) { 454 const auto prepare_stage{[&](size_t stage) {
453 buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]); 455 buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]);
454 buffer_cache.BindHostStageBuffers(stage); 456 buffer_cache.BindHostStageBuffers(stage);
@@ -465,6 +467,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
465 u32 stage_image_binding{}; 467 u32 stage_image_binding{};
466 468
467 const auto& info{stage_infos[stage]}; 469 const auto& info{stage_infos[stage]};
470 if constexpr (Spec::has_texture_buffers) {
471 for (const auto& desc : info.texture_buffer_descriptors) {
472 for (u32 index = 0; index < desc.count; ++index) {
473 gl_samplers[sampler_binding++] = 0;
474 }
475 }
476 }
468 for (const auto& desc : info.texture_descriptors) { 477 for (const auto& desc : info.texture_descriptors) {
469 for (u32 index = 0; index < desc.count; ++index) { 478 for (u32 index = 0; index < desc.count; ++index) {
470 ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; 479 ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
@@ -474,6 +483,12 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
474 } 483 }
475 ++texture_binding; 484 ++texture_binding;
476 ++stage_texture_binding; 485 ++stage_texture_binding;
486
487 const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))};
488 const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
489 !image_view.SupportsAnisotropy()};
490 gl_samplers[sampler_binding++] =
491 use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle();
477 } 492 }
478 } 493 }
479 for (const auto& desc : info.image_descriptors) { 494 for (const auto& desc : info.image_descriptors) {
@@ -534,7 +549,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
534 if (texture_binding != 0) { 549 if (texture_binding != 0) {
535 ASSERT(texture_binding == sampler_binding); 550 ASSERT(texture_binding == sampler_binding);
536 glBindTextures(0, texture_binding, textures.data()); 551 glBindTextures(0, texture_binding, textures.data());
537 glBindSamplers(0, sampler_binding, samplers.data()); 552 glBindSamplers(0, sampler_binding, gl_samplers.data());
538 } 553 }
539 if (image_binding != 0) { 554 if (image_binding != 0) {
540 glBindImageTextures(0, image_binding, images.data()); 555 glBindImageTextures(0, image_binding, images.data());
diff --git a/src/video_core/renderer_opengl/gl_shader_context.h b/src/video_core/renderer_opengl/gl_shader_context.h
index 207a75d42..d12cd06fa 100644
--- a/src/video_core/renderer_opengl/gl_shader_context.h
+++ b/src/video_core/renderer_opengl/gl_shader_context.h
@@ -16,9 +16,9 @@ struct ShaderPools {
16 inst.ReleaseContents(); 16 inst.ReleaseContents();
17 } 17 }
18 18
19 Shader::ObjectPool<Shader::IR::Inst> inst; 19 Shader::ObjectPool<Shader::IR::Inst> inst{8192};
20 Shader::ObjectPool<Shader::IR::Block> block; 20 Shader::ObjectPool<Shader::IR::Block> block{32};
21 Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; 21 Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
22}; 22};
23 23
24struct Context { 24struct Context {
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 1c5dbcdd8..3b446be07 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -1268,36 +1268,48 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) {
1268 1268
1269 UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1); 1269 UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1);
1270 1270
1271 sampler.Create(); 1271 const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f);
1272 const GLuint handle = sampler.handle; 1272
1273 glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u)); 1273 const auto create_sampler = [&](const f32 anisotropy) {
1274 glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v)); 1274 OGLSampler new_sampler;
1275 glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p)); 1275 new_sampler.Create();
1276 glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode); 1276 const GLuint handle = new_sampler.handle;
1277 glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func); 1277 glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u));
1278 glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag); 1278 glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v));
1279 glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min); 1279 glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p));
1280 glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias()); 1280 glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode);
1281 glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod()); 1281 glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func);
1282 glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod()); 1282 glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag);
1283 glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data()); 1283 glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min);
1284 1284 glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias());
1285 if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) { 1285 glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod());
1286 const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f); 1286 glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod());
1287 glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropy); 1287 glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data());
1288 } else { 1288
1289 LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required"); 1289 if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) {
1290 } 1290 glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, anisotropy);
1291 if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) { 1291 } else {
1292 glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter); 1292 LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required");
1293 } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) { 1293 }
1294 LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required"); 1294 if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) {
1295 } 1295 glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter);
1296 if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) { 1296 } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) {
1297 glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless); 1297 LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required");
1298 } else if (seamless == GL_FALSE) { 1298 }
1299 // We default to false because it's more common 1299 if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) {
1300 LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required"); 1300 glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless);
1301 } else if (seamless == GL_FALSE) {
1302 // We default to false because it's more common
1303 LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required");
1304 }
1305 return new_sampler;
1306 };
1307
1308 sampler = create_sampler(max_anisotropy);
1309
1310 const f32 max_anisotropy_default = static_cast<f32>(1U << config.max_anisotropy);
1311 if (max_anisotropy > max_anisotropy_default) {
1312 sampler_default_anisotropy = create_sampler(max_anisotropy_default);
1301 } 1313 }
1302} 1314}
1303 1315
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 1148b73d7..3676eaaa9 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -309,12 +309,21 @@ class Sampler {
309public: 309public:
310 explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); 310 explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
311 311
312 GLuint Handle() const noexcept { 312 [[nodiscard]] GLuint Handle() const noexcept {
313 return sampler.handle; 313 return sampler.handle;
314 } 314 }
315 315
316 [[nodiscard]] GLuint HandleWithDefaultAnisotropy() const noexcept {
317 return sampler_default_anisotropy.handle;
318 }
319
320 [[nodiscard]] bool HasAddedAnisotropy() const noexcept {
321 return static_cast<bool>(sampler_default_anisotropy.handle);
322 }
323
316private: 324private:
317 OGLSampler sampler; 325 OGLSampler sampler;
326 OGLSampler sampler_default_anisotropy;
318}; 327};
319 328
320class Framebuffer { 329class Framebuffer {
diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h
index 983e1c2e1..71c783709 100644
--- a/src/video_core/renderer_vulkan/pipeline_helper.h
+++ b/src/video_core/renderer_vulkan/pipeline_helper.h
@@ -178,7 +178,7 @@ public:
178inline void PushImageDescriptors(TextureCache& texture_cache, 178inline void PushImageDescriptors(TextureCache& texture_cache,
179 GuestDescriptorQueue& guest_descriptor_queue, 179 GuestDescriptorQueue& guest_descriptor_queue,
180 const Shader::Info& info, RescalingPushConstant& rescaling, 180 const Shader::Info& info, RescalingPushConstant& rescaling,
181 const VkSampler*& samplers, 181 const VideoCommon::SamplerId*& samplers,
182 const VideoCommon::ImageViewInOut*& views) { 182 const VideoCommon::ImageViewInOut*& views) {
183 const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors); 183 const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors);
184 const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors); 184 const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors);
@@ -187,10 +187,15 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
187 for (const auto& desc : info.texture_descriptors) { 187 for (const auto& desc : info.texture_descriptors) {
188 for (u32 index = 0; index < desc.count; ++index) { 188 for (u32 index = 0; index < desc.count; ++index) {
189 const VideoCommon::ImageViewId image_view_id{(views++)->id}; 189 const VideoCommon::ImageViewId image_view_id{(views++)->id};
190 const VkSampler sampler{*(samplers++)}; 190 const VideoCommon::SamplerId sampler_id{*(samplers++)};
191 ImageView& image_view{texture_cache.GetImageView(image_view_id)}; 191 ImageView& image_view{texture_cache.GetImageView(image_view_id)};
192 const VkImageView vk_image_view{image_view.Handle(desc.type)}; 192 const VkImageView vk_image_view{image_view.Handle(desc.type)};
193 guest_descriptor_queue.AddSampledImage(vk_image_view, sampler); 193 const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
194 const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
195 !image_view.SupportsAnisotropy()};
196 const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
197 : sampler.Handle()};
198 guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
194 rescaling.PushTexture(texture_cache.IsRescaling(image_view)); 199 rescaling.PushTexture(texture_cache.IsRescaling(image_view));
195 } 200 }
196 } 201 }
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index d72d99899..e30fcb1ed 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -501,11 +501,10 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
501 } 501 }
502} 502}
503 503
504void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { 504void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
505 boost::container::small_vector<VkBuffer, 32> buffer_handles; 505 boost::container::small_vector<VkBuffer, 32> buffer_handles;
506 for (u32 index = 0; index < bindings.buffers.size(); index++) { 506 for (u32 index = 0; index < bindings.buffers.size(); ++index) {
507 auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]); 507 auto handle = bindings.buffers[index]->Handle();
508 auto handle = buffer.Handle();
509 if (handle == VK_NULL_HANDLE) { 508 if (handle == VK_NULL_HANDLE) {
510 bindings.offsets[index] = 0; 509 bindings.offsets[index] = 0;
511 bindings.sizes[index] = VK_WHOLE_SIZE; 510 bindings.sizes[index] = VK_WHOLE_SIZE;
@@ -517,20 +516,17 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings)
517 buffer_handles.push_back(handle); 516 buffer_handles.push_back(handle);
518 } 517 }
519 if (device.IsExtExtendedDynamicStateSupported()) { 518 if (device.IsExtExtendedDynamicStateSupported()) {
520 scheduler.Record([bindings = bindings, 519 scheduler.Record([bindings = std::move(bindings),
521 buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { 520 buffer_handles = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
522 cmdbuf.BindVertexBuffers2EXT( 521 cmdbuf.BindVertexBuffers2EXT(
523 bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), 522 bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
524 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()), 523 bindings.offsets.data(), bindings.sizes.data(), bindings.strides.data());
525 reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()),
526 reinterpret_cast<const VkDeviceSize*>(bindings.strides.data()));
527 }); 524 });
528 } else { 525 } else {
529 scheduler.Record([bindings = bindings, 526 scheduler.Record([bindings = std::move(bindings),
530 buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { 527 buffer_handles = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
531 cmdbuf.BindVertexBuffers( 528 cmdbuf.BindVertexBuffers(bindings.min_index, bindings.max_index - bindings.min_index,
532 bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), 529 buffer_handles.data(), bindings.offsets.data());
533 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()));
534 }); 530 });
535 } 531 }
536} 532}
@@ -556,23 +552,21 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer,
556 }); 552 });
557} 553}
558 554
559void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) { 555void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
560 if (!device.IsExtTransformFeedbackSupported()) { 556 if (!device.IsExtTransformFeedbackSupported()) {
561 // Already logged in the rasterizer 557 // Already logged in the rasterizer
562 return; 558 return;
563 } 559 }
564 boost::container::small_vector<VkBuffer, 4> buffer_handles; 560 boost::container::small_vector<VkBuffer, 4> buffer_handles;
565 for (u32 index = 0; index < bindings.buffers.size(); index++) { 561 for (u32 index = 0; index < bindings.buffers.size(); ++index) {
566 auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]); 562 buffer_handles.push_back(bindings.buffers[index]->Handle());
567 buffer_handles.push_back(buffer.Handle()); 563 }
568 } 564 scheduler.Record([bindings = std::move(bindings),
569 scheduler.Record( 565 buffer_handles = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
570 [bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { 566 cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast<u32>(buffer_handles.size()),
571 cmdbuf.BindTransformFeedbackBuffersEXT( 567 buffer_handles.data(), bindings.offsets.data(),
572 0, static_cast<u32>(buffer_handles.size()), buffer_handles.data(), 568 bindings.sizes.data());
573 reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()), 569 });
574 reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()));
575 });
576} 570}
577 571
578void BufferCacheRuntime::ReserveNullBuffer() { 572void BufferCacheRuntime::ReserveNullBuffer() {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 92d3e9f32..cdeef8846 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -97,10 +97,12 @@ public:
97 void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); 97 void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
98 98
99 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); 100
101 void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
101 102
102 void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); 103 void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
103 void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); 104
105 void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
104 106
105 std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, 107 std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
106 [[maybe_unused]] u32 binding_index, u32 size) { 108 [[maybe_unused]] u32 binding_index, u32 size) {
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index 733e70d9d..73e585c2b 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -115,7 +115,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
115 115
116 static constexpr size_t max_elements = 64; 116 static constexpr size_t max_elements = 64;
117 boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views; 117 boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views;
118 boost::container::static_vector<VkSampler, max_elements> samplers; 118 boost::container::static_vector<VideoCommon::SamplerId, max_elements> samplers;
119 119
120 const auto& qmd{kepler_compute.launch_description}; 120 const auto& qmd{kepler_compute.launch_description};
121 const auto& cbufs{qmd.const_buffer_config}; 121 const auto& cbufs{qmd.const_buffer_config};
@@ -160,8 +160,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
160 const auto handle{read_handle(desc, index)}; 160 const auto handle{read_handle(desc, index)};
161 views.push_back({handle.first}); 161 views.push_back({handle.first});
162 162
163 Sampler* const sampler = texture_cache.GetComputeSampler(handle.second); 163 VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second);
164 samplers.push_back(sampler->Handle()); 164 samplers.push_back(sampler);
165 } 165 }
166 } 166 }
167 for (const auto& desc : info.image_descriptors) { 167 for (const auto& desc : info.image_descriptors) {
@@ -192,7 +192,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
192 buffer_cache.BindHostComputeBuffers(); 192 buffer_cache.BindHostComputeBuffers();
193 193
194 RescalingPushConstant rescaling; 194 RescalingPushConstant rescaling;
195 const VkSampler* samplers_it{samplers.data()}; 195 const VideoCommon::SamplerId* samplers_it{samplers.data()};
196 const VideoCommon::ImageViewInOut* views_it{views.data()}; 196 const VideoCommon::ImageViewInOut* views_it{views.data()};
197 PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it, 197 PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it,
198 views_it); 198 views_it);
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 506b78f08..c1595642e 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -298,7 +298,7 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) {
298template <typename Spec> 298template <typename Spec>
299void GraphicsPipeline::ConfigureImpl(bool is_indexed) { 299void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
300 std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views; 300 std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views;
301 std::array<VkSampler, MAX_IMAGE_ELEMENTS> samplers; 301 std::array<VideoCommon::SamplerId, MAX_IMAGE_ELEMENTS> samplers;
302 size_t sampler_index{}; 302 size_t sampler_index{};
303 size_t view_index{}; 303 size_t view_index{};
304 304
@@ -367,8 +367,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
367 const auto handle{read_handle(desc, index)}; 367 const auto handle{read_handle(desc, index)};
368 views[view_index++] = {handle.first}; 368 views[view_index++] = {handle.first};
369 369
370 Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)}; 370 VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)};
371 samplers[sampler_index++] = sampler->Handle(); 371 samplers[sampler_index++] = sampler;
372 } 372 }
373 } 373 }
374 if constexpr (Spec::has_images) { 374 if constexpr (Spec::has_images) {
@@ -453,7 +453,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
453 453
454 RescalingPushConstant rescaling; 454 RescalingPushConstant rescaling;
455 RenderAreaPushConstant render_area; 455 RenderAreaPushConstant render_area;
456 const VkSampler* samplers_it{samplers.data()}; 456 const VideoCommon::SamplerId* samplers_it{samplers.data()};
457 const VideoCommon::ImageViewInOut* views_it{views.data()}; 457 const VideoCommon::ImageViewInOut* views_it{views.data()};
458 const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE { 458 const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
459 buffer_cache.BindHostStageBuffers(stage); 459 buffer_cache.BindHostStageBuffers(stage);
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
index b128c4f6e..5eeda08d2 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -3,6 +3,7 @@
3 3
4#include <thread> 4#include <thread>
5 5
6#include "common/polyfill_ranges.h"
6#include "common/settings.h" 7#include "common/settings.h"
7#include "video_core/renderer_vulkan/vk_master_semaphore.h" 8#include "video_core/renderer_vulkan/vk_master_semaphore.h"
8#include "video_core/vulkan_common/vulkan_device.h" 9#include "video_core/vulkan_common/vulkan_device.h"
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 5734f51e5..a2cfb2105 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -358,6 +358,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
358 .support_snorm_render_buffer = true, 358 .support_snorm_render_buffer = true,
359 .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), 359 .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
360 .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), 360 .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
361 .support_conditional_barrier = device.SupportsConditionalBarriers(),
361 }; 362 };
362 363
363 if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) { 364 if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) {
@@ -704,10 +705,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
704std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( 705std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
705 ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env, 706 ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env,
706 PipelineStatistics* statistics, bool build_in_parallel) try { 707 PipelineStatistics* statistics, bool build_in_parallel) try {
707 // TODO: Remove this when Intel fixes their shader compiler. 708 if (device.HasBrokenCompute()) {
708 // https://github.com/IGCIT/Intel-GPU-Community-Issue-Tracker-IGCIT/issues/159
709 if (device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS &&
710 !Settings::values.enable_compute_pipelines.GetValue()) {
711 LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", key.Hash()); 709 LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", key.Hash());
712 return nullptr; 710 return nullptr;
713 } 711 }
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index 15aa7e224..e323ea0fd 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -92,9 +92,9 @@ struct ShaderPools {
92 inst.ReleaseContents(); 92 inst.ReleaseContents();
93 } 93 }
94 94
95 Shader::ObjectPool<Shader::IR::Inst> inst; 95 Shader::ObjectPool<Shader::IR::Inst> inst{8192};
96 Shader::ObjectPool<Shader::IR::Block> block; 96 Shader::ObjectPool<Shader::IR::Block> block{32};
97 Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; 97 Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
98}; 98};
99 99
100class PipelineCache : public VideoCommon::ShaderCache { 100class PipelineCache : public VideoCommon::ShaderCache {
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 8711e2a87..f025f618b 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1802,27 +1802,36 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
1802 // Some games have samplers with garbage. Sanitize them here. 1802 // Some games have samplers with garbage. Sanitize them here.
1803 const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); 1803 const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f);
1804 1804
1805 sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{ 1805 const auto create_sampler = [&](const f32 anisotropy) {
1806 .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, 1806 return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
1807 .pNext = pnext, 1807 .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
1808 .flags = 0, 1808 .pNext = pnext,
1809 .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), 1809 .flags = 0,
1810 .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), 1810 .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter),
1811 .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), 1811 .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter),
1812 .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), 1812 .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
1813 .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), 1813 .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
1814 .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), 1814 .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
1815 .mipLodBias = tsc.LodBias(), 1815 .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
1816 .anisotropyEnable = static_cast<VkBool32>(max_anisotropy > 1.0f ? VK_TRUE : VK_FALSE), 1816 .mipLodBias = tsc.LodBias(),
1817 .maxAnisotropy = max_anisotropy, 1817 .anisotropyEnable = static_cast<VkBool32>(anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
1818 .compareEnable = tsc.depth_compare_enabled, 1818 .maxAnisotropy = anisotropy,
1819 .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), 1819 .compareEnable = tsc.depth_compare_enabled,
1820 .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), 1820 .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
1821 .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), 1821 .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(),
1822 .borderColor = 1822 .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(),
1823 arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color), 1823 .borderColor =
1824 .unnormalizedCoordinates = VK_FALSE, 1824 arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color),
1825 }); 1825 .unnormalizedCoordinates = VK_FALSE,
1826 });
1827 };
1828
1829 sampler = create_sampler(max_anisotropy);
1830
1831 const f32 max_anisotropy_default = static_cast<f32>(1U << tsc.max_anisotropy);
1832 if (max_anisotropy > max_anisotropy_default) {
1833 sampler_default_anisotropy = create_sampler(max_anisotropy_default);
1834 }
1826} 1835}
1827 1836
1828Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers, 1837Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers,
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 0f7a5ffd4..f14525dcb 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -279,8 +279,17 @@ public:
279 return *sampler; 279 return *sampler;
280 } 280 }
281 281
282 [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept {
283 return *sampler_default_anisotropy;
284 }
285
286 [[nodiscard]] bool HasAddedAnisotropy() const noexcept {
287 return static_cast<bool>(sampler_default_anisotropy);
288 }
289
282private: 290private:
283 vk::Sampler sampler; 291 vk::Sampler sampler;
292 vk::Sampler sampler_default_anisotropy;
284}; 293};
285 294
286class Framebuffer { 295class Framebuffer {
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
index d134b6738..0c5f4450d 100644
--- a/src/video_core/texture_cache/image_view_base.cpp
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -45,4 +45,56 @@ ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_in
45 45
46ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {} 46ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {}
47 47
48bool ImageViewBase::SupportsAnisotropy() const noexcept {
49 const bool has_mips = range.extent.levels > 1;
50 const bool is_2d = type == ImageViewType::e2D || type == ImageViewType::e2DArray;
51 if (!has_mips || !is_2d) {
52 return false;
53 }
54
55 switch (format) {
56 case PixelFormat::R8_UNORM:
57 case PixelFormat::R8_SNORM:
58 case PixelFormat::R8_SINT:
59 case PixelFormat::R8_UINT:
60 case PixelFormat::BC4_UNORM:
61 case PixelFormat::BC4_SNORM:
62 case PixelFormat::BC5_UNORM:
63 case PixelFormat::BC5_SNORM:
64 case PixelFormat::R32G32_FLOAT:
65 case PixelFormat::R32G32_SINT:
66 case PixelFormat::R32_FLOAT:
67 case PixelFormat::R16_FLOAT:
68 case PixelFormat::R16_UNORM:
69 case PixelFormat::R16_SNORM:
70 case PixelFormat::R16_UINT:
71 case PixelFormat::R16_SINT:
72 case PixelFormat::R16G16_UNORM:
73 case PixelFormat::R16G16_FLOAT:
74 case PixelFormat::R16G16_UINT:
75 case PixelFormat::R16G16_SINT:
76 case PixelFormat::R16G16_SNORM:
77 case PixelFormat::R8G8_UNORM:
78 case PixelFormat::R8G8_SNORM:
79 case PixelFormat::R8G8_SINT:
80 case PixelFormat::R8G8_UINT:
81 case PixelFormat::R32G32_UINT:
82 case PixelFormat::R32_UINT:
83 case PixelFormat::R32_SINT:
84 case PixelFormat::G4R4_UNORM:
85 // Depth formats
86 case PixelFormat::D32_FLOAT:
87 case PixelFormat::D16_UNORM:
88 // Stencil formats
89 case PixelFormat::S8_UINT:
90 // DepthStencil formats
91 case PixelFormat::D24_UNORM_S8_UINT:
92 case PixelFormat::S8_UINT_D24_UNORM:
93 case PixelFormat::D32_FLOAT_S8_UINT:
94 return false;
95 default:
96 return true;
97 }
98}
99
48} // namespace VideoCommon 100} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h
index a25ae1d4a..87549ffff 100644
--- a/src/video_core/texture_cache/image_view_base.h
+++ b/src/video_core/texture_cache/image_view_base.h
@@ -33,6 +33,8 @@ struct ImageViewBase {
33 return type == ImageViewType::Buffer; 33 return type == ImageViewType::Buffer;
34 } 34 }
35 35
36 [[nodiscard]] bool SupportsAnisotropy() const noexcept;
37
36 ImageId image_id{}; 38 ImageId image_id{};
37 GPUVAddr gpu_addr = 0; 39 GPUVAddr gpu_addr = 0;
38 PixelFormat format{}; 40 PixelFormat format{};
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 43b7ac0a6..d58bb69ff 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -226,30 +226,50 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) {
226 226
227template <class P> 227template <class P>
228typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { 228typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) {
229 return &slot_samplers[GetGraphicsSamplerId(index)];
230}
231
232template <class P>
233typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) {
234 return &slot_samplers[GetComputeSamplerId(index)];
235}
236
237template <class P>
238SamplerId TextureCache<P>::GetGraphicsSamplerId(u32 index) {
229 if (index > channel_state->graphics_sampler_table.Limit()) { 239 if (index > channel_state->graphics_sampler_table.Limit()) {
230 LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); 240 LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index);
231 return &slot_samplers[NULL_SAMPLER_ID]; 241 return NULL_SAMPLER_ID;
232 } 242 }
233 const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index); 243 const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index);
234 SamplerId& id = channel_state->graphics_sampler_ids[index]; 244 SamplerId& id = channel_state->graphics_sampler_ids[index];
235 if (is_new) { 245 if (is_new) {
236 id = FindSampler(descriptor); 246 id = FindSampler(descriptor);
237 } 247 }
238 return &slot_samplers[id]; 248 return id;
239} 249}
240 250
241template <class P> 251template <class P>
242typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) { 252SamplerId TextureCache<P>::GetComputeSamplerId(u32 index) {
243 if (index > channel_state->compute_sampler_table.Limit()) { 253 if (index > channel_state->compute_sampler_table.Limit()) {
244 LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); 254 LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index);
245 return &slot_samplers[NULL_SAMPLER_ID]; 255 return NULL_SAMPLER_ID;
246 } 256 }
247 const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index); 257 const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index);
248 SamplerId& id = channel_state->compute_sampler_ids[index]; 258 SamplerId& id = channel_state->compute_sampler_ids[index];
249 if (is_new) { 259 if (is_new) {
250 id = FindSampler(descriptor); 260 id = FindSampler(descriptor);
251 } 261 }
252 return &slot_samplers[id]; 262 return id;
263}
264
265template <class P>
266const typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) const noexcept {
267 return slot_samplers[id];
268}
269
270template <class P>
271typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) noexcept {
272 return slot_samplers[id];
253} 273}
254 274
255template <class P> 275template <class P>
@@ -284,7 +304,7 @@ void TextureCache<P>::SynchronizeComputeDescriptors() {
284} 304}
285 305
286template <class P> 306template <class P>
287bool TextureCache<P>::RescaleRenderTargets(bool is_clear) { 307bool TextureCache<P>::RescaleRenderTargets() {
288 auto& flags = maxwell3d->dirty.flags; 308 auto& flags = maxwell3d->dirty.flags;
289 u32 scale_rating = 0; 309 u32 scale_rating = 0;
290 bool rescaled = false; 310 bool rescaled = false;
@@ -322,13 +342,13 @@ bool TextureCache<P>::RescaleRenderTargets(bool is_clear) {
322 ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; 342 ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index];
323 if (flags[Dirty::ColorBuffer0 + index] || force) { 343 if (flags[Dirty::ColorBuffer0 + index] || force) {
324 flags[Dirty::ColorBuffer0 + index] = false; 344 flags[Dirty::ColorBuffer0 + index] = false;
325 BindRenderTarget(&color_buffer_id, FindColorBuffer(index, is_clear)); 345 BindRenderTarget(&color_buffer_id, FindColorBuffer(index));
326 } 346 }
327 check_rescale(color_buffer_id, tmp_color_images[index]); 347 check_rescale(color_buffer_id, tmp_color_images[index]);
328 } 348 }
329 if (flags[Dirty::ZetaBuffer] || force) { 349 if (flags[Dirty::ZetaBuffer] || force) {
330 flags[Dirty::ZetaBuffer] = false; 350 flags[Dirty::ZetaBuffer] = false;
331 BindRenderTarget(&render_targets.depth_buffer_id, FindDepthBuffer(is_clear)); 351 BindRenderTarget(&render_targets.depth_buffer_id, FindDepthBuffer());
332 } 352 }
333 check_rescale(render_targets.depth_buffer_id, tmp_depth_image); 353 check_rescale(render_targets.depth_buffer_id, tmp_depth_image);
334 354
@@ -393,7 +413,7 @@ void TextureCache<P>::UpdateRenderTargets(bool is_clear) {
393 return; 413 return;
394 } 414 }
395 415
396 const bool rescaled = RescaleRenderTargets(is_clear); 416 const bool rescaled = RescaleRenderTargets();
397 if (is_rescaling != rescaled) { 417 if (is_rescaling != rescaled) {
398 flags[Dirty::RescaleViewports] = true; 418 flags[Dirty::RescaleViewports] = true;
399 flags[Dirty::RescaleScissors] = true; 419 flags[Dirty::RescaleScissors] = true;
@@ -1662,7 +1682,7 @@ SamplerId TextureCache<P>::FindSampler(const TSCEntry& config) {
1662} 1682}
1663 1683
1664template <class P> 1684template <class P>
1665ImageViewId TextureCache<P>::FindColorBuffer(size_t index, bool is_clear) { 1685ImageViewId TextureCache<P>::FindColorBuffer(size_t index) {
1666 const auto& regs = maxwell3d->regs; 1686 const auto& regs = maxwell3d->regs;
1667 if (index >= regs.rt_control.count) { 1687 if (index >= regs.rt_control.count) {
1668 return ImageViewId{}; 1688 return ImageViewId{};
@@ -1676,11 +1696,11 @@ ImageViewId TextureCache<P>::FindColorBuffer(size_t index, bool is_clear) {
1676 return ImageViewId{}; 1696 return ImageViewId{};
1677 } 1697 }
1678 const ImageInfo info(regs.rt[index], regs.anti_alias_samples_mode); 1698 const ImageInfo info(regs.rt[index], regs.anti_alias_samples_mode);
1679 return FindRenderTargetView(info, gpu_addr, is_clear); 1699 return FindRenderTargetView(info, gpu_addr);
1680} 1700}
1681 1701
1682template <class P> 1702template <class P>
1683ImageViewId TextureCache<P>::FindDepthBuffer(bool is_clear) { 1703ImageViewId TextureCache<P>::FindDepthBuffer() {
1684 const auto& regs = maxwell3d->regs; 1704 const auto& regs = maxwell3d->regs;
1685 if (!regs.zeta_enable) { 1705 if (!regs.zeta_enable) {
1686 return ImageViewId{}; 1706 return ImageViewId{};
@@ -1690,18 +1710,16 @@ ImageViewId TextureCache<P>::FindDepthBuffer(bool is_clear) {
1690 return ImageViewId{}; 1710 return ImageViewId{};
1691 } 1711 }
1692 const ImageInfo info(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode); 1712 const ImageInfo info(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode);
1693 return FindRenderTargetView(info, gpu_addr, is_clear); 1713 return FindRenderTargetView(info, gpu_addr);
1694} 1714}
1695 1715
1696template <class P> 1716template <class P>
1697ImageViewId TextureCache<P>::FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, 1717ImageViewId TextureCache<P>::FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr) {
1698 bool is_clear) {
1699 const auto options = is_clear ? RelaxedOptions::Samples : RelaxedOptions{};
1700 ImageId image_id{}; 1718 ImageId image_id{};
1701 bool delete_state = has_deleted_images; 1719 bool delete_state = has_deleted_images;
1702 do { 1720 do {
1703 has_deleted_images = false; 1721 has_deleted_images = false;
1704 image_id = FindOrInsertImage(info, gpu_addr, options); 1722 image_id = FindOrInsertImage(info, gpu_addr);
1705 delete_state |= has_deleted_images; 1723 delete_state |= has_deleted_images;
1706 } while (has_deleted_images); 1724 } while (has_deleted_images);
1707 has_deleted_images = delete_state; 1725 has_deleted_images = delete_state;
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index 3bfa92154..44232b961 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -159,6 +159,18 @@ public:
159 /// Get the sampler from the compute descriptor table in the specified index 159 /// Get the sampler from the compute descriptor table in the specified index
160 Sampler* GetComputeSampler(u32 index); 160 Sampler* GetComputeSampler(u32 index);
161 161
162 /// Get the sampler id from the graphics descriptor table in the specified index
163 SamplerId GetGraphicsSamplerId(u32 index);
164
165 /// Get the sampler id from the compute descriptor table in the specified index
166 SamplerId GetComputeSamplerId(u32 index);
167
168 /// Return a constant reference to the given sampler id
169 [[nodiscard]] const Sampler& GetSampler(SamplerId id) const noexcept;
170
171 /// Return a reference to the given sampler id
172 [[nodiscard]] Sampler& GetSampler(SamplerId id) noexcept;
173
162 /// Refresh the state for graphics image view and sampler descriptors 174 /// Refresh the state for graphics image view and sampler descriptors
163 void SynchronizeGraphicsDescriptors(); 175 void SynchronizeGraphicsDescriptors();
164 176
@@ -166,9 +178,8 @@ public:
166 void SynchronizeComputeDescriptors(); 178 void SynchronizeComputeDescriptors();
167 179
168 /// Updates the Render Targets if they can be rescaled 180 /// Updates the Render Targets if they can be rescaled
169 /// @param is_clear True when the render targets are being used for clears
170 /// @retval True if the Render Targets have been rescaled. 181 /// @retval True if the Render Targets have been rescaled.
171 bool RescaleRenderTargets(bool is_clear); 182 bool RescaleRenderTargets();
172 183
173 /// Update bound render targets and upload memory if necessary 184 /// Update bound render targets and upload memory if necessary
174 /// @param is_clear True when the render targets are being used for clears 185 /// @param is_clear True when the render targets are being used for clears
@@ -324,14 +335,13 @@ private:
324 [[nodiscard]] SamplerId FindSampler(const TSCEntry& config); 335 [[nodiscard]] SamplerId FindSampler(const TSCEntry& config);
325 336
326 /// Find or create an image view for the given color buffer index 337 /// Find or create an image view for the given color buffer index
327 [[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear); 338 [[nodiscard]] ImageViewId FindColorBuffer(size_t index);
328 339
329 /// Find or create an image view for the depth buffer 340 /// Find or create an image view for the depth buffer
330 [[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear); 341 [[nodiscard]] ImageViewId FindDepthBuffer();
331 342
332 /// Find or create a view for a render target with the given image parameters 343 /// Find or create a view for a render target with the given image parameters
333 [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, 344 [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr);
334 bool is_clear);
335 345
336 /// Iterates over all the images in a region calling func 346 /// Iterates over all the images in a region calling func
337 template <typename Func> 347 template <typename Func>
diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp
index 4a80a59f9..d8b88d9bc 100644
--- a/src/video_core/textures/texture.cpp
+++ b/src/video_core/textures/texture.cpp
@@ -62,7 +62,12 @@ std::array<float, 4> TSCEntry::BorderColor() const noexcept {
62} 62}
63 63
64float TSCEntry::MaxAnisotropy() const noexcept { 64float TSCEntry::MaxAnisotropy() const noexcept {
65 if (max_anisotropy == 0 && mipmap_filter != TextureMipmapFilter::Linear) { 65 const bool is_suitable_mipmap_filter = mipmap_filter != TextureMipmapFilter::None;
66 const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256;
67 const bool is_bilinear_filter = min_filter == TextureFilter::Linear &&
68 reduction_filter == SamplerReduction::WeightedAverage;
69 if (max_anisotropy == 0 && (!is_suitable_mipmap_filter || !has_regular_lods ||
70 !is_bilinear_filter || depth_compare_enabled)) {
66 return 1.0f; 71 return 1.0f;
67 } 72 }
68 const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue(); 73 const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue();
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index a46f9beed..fa9cde75b 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -344,6 +344,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
344 const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY; 344 const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
345 const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP; 345 const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP;
346 const bool is_s8gen2 = device_id == 0x43050a01; 346 const bool is_s8gen2 = device_id == 0x43050a01;
347 const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
347 348
348 if ((is_mvk || is_qualcomm || is_turnip) && !is_suitable) { 349 if ((is_mvk || is_qualcomm || is_turnip) && !is_suitable) {
349 LOG_WARNING(Render_Vulkan, "Unsuitable driver, continuing anyway"); 350 LOG_WARNING(Render_Vulkan, "Unsuitable driver, continuing anyway");
@@ -391,7 +392,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
391 CollectPhysicalMemoryInfo(); 392 CollectPhysicalMemoryInfo();
392 CollectToolingInfo(); 393 CollectToolingInfo();
393 394
394#ifdef ANDROID
395 if (is_qualcomm || is_turnip) { 395 if (is_qualcomm || is_turnip) {
396 LOG_WARNING(Render_Vulkan, 396 LOG_WARNING(Render_Vulkan,
397 "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); 397 "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color");
@@ -411,7 +411,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
411 extensions.push_descriptor = false; 411 extensions.push_descriptor = false;
412 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); 412 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
413 413
414#ifdef ARCHITECTURE_arm64 414#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
415 // Patch the driver to enable BCn textures. 415 // Patch the driver to enable BCn textures.
416 const auto major = (properties.properties.driverVersion >> 24) << 2; 416 const auto major = (properties.properties.driverVersion >> 24) << 2;
417 const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU; 417 const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU;
@@ -431,18 +431,23 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
431 } else { 431 } else {
432 LOG_WARNING(Render_Vulkan, "Adreno driver can't be patched to enable BCn textures"); 432 LOG_WARNING(Render_Vulkan, "Adreno driver can't be patched to enable BCn textures");
433 } 433 }
434#endif // ARCHITECTURE_arm64 434#endif
435 } 435 }
436 436
437 const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
438 if (is_arm) { 437 if (is_arm) {
439 must_emulate_scaled_formats = true; 438 must_emulate_scaled_formats = true;
440 439
441 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); 440 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
442 extensions.extended_dynamic_state = false; 441 extensions.extended_dynamic_state = false;
443 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 442 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
443
444 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2");
445 features.extended_dynamic_state2.extendedDynamicState2 = false;
446 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false;
447 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
448 extensions.extended_dynamic_state2 = false;
449 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
444 } 450 }
445#endif // ANDROID
446 451
447 if (is_nvidia) { 452 if (is_nvidia) {
448 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; 453 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
@@ -557,6 +562,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
557 LOG_WARNING(Render_Vulkan, "Intel proprietary drivers do not support MSAA image blits"); 562 LOG_WARNING(Render_Vulkan, "Intel proprietary drivers do not support MSAA image blits");
558 cant_blit_msaa = true; 563 cant_blit_msaa = true;
559 } 564 }
565 has_broken_compute =
566 CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) &&
567 !Settings::values.enable_compute_pipelines.GetValue();
560 if (is_intel_anv || (is_qualcomm && !is_s8gen2)) { 568 if (is_intel_anv || (is_qualcomm && !is_s8gen2)) {
561 LOG_WARNING(Render_Vulkan, "Driver does not support native BGR format"); 569 LOG_WARNING(Render_Vulkan, "Driver does not support native BGR format");
562 must_emulate_bgr565 = true; 570 must_emulate_bgr565 = true;
@@ -778,9 +786,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
778 786
779 FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION); 787 FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
780 FOR_EACH_VK_EXTENSION(EXTENSION); 788 FOR_EACH_VK_EXTENSION(EXTENSION);
781#ifdef _WIN32
782 FOR_EACH_VK_EXTENSION_WIN32(EXTENSION);
783#endif
784 789
785#undef FEATURE_EXTENSION 790#undef FEATURE_EXTENSION
786#undef EXTENSION 791#undef EXTENSION
@@ -799,11 +804,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
799 804
800 FOR_EACH_VK_RECOMMENDED_EXTENSION(LOG_EXTENSION); 805 FOR_EACH_VK_RECOMMENDED_EXTENSION(LOG_EXTENSION);
801 FOR_EACH_VK_MANDATORY_EXTENSION(CHECK_EXTENSION); 806 FOR_EACH_VK_MANDATORY_EXTENSION(CHECK_EXTENSION);
802#ifdef _WIN32
803 FOR_EACH_VK_MANDATORY_EXTENSION_WIN32(CHECK_EXTENSION);
804#else
805 FOR_EACH_VK_MANDATORY_EXTENSION_GENERIC(CHECK_EXTENSION);
806#endif
807 807
808 if (requires_swapchain) { 808 if (requires_swapchain) {
809 CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME); 809 CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index f314d0ffe..0b634a876 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -10,6 +10,7 @@
10#include <vector> 10#include <vector>
11 11
12#include "common/common_types.h" 12#include "common/common_types.h"
13#include "common/logging/log.h"
13#include "common/settings.h" 14#include "common/settings.h"
14#include "video_core/vulkan_common/vulkan_wrapper.h" 15#include "video_core/vulkan_common/vulkan_wrapper.h"
15 16
@@ -68,7 +69,6 @@
68 EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \ 69 EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \
69 EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \ 70 EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \
70 EXTENSION(KHR, DRIVER_PROPERTIES, driver_properties) \ 71 EXTENSION(KHR, DRIVER_PROPERTIES, driver_properties) \
71 EXTENSION(KHR, EXTERNAL_MEMORY_FD, external_memory_fd) \
72 EXTENSION(KHR, PUSH_DESCRIPTOR, push_descriptor) \ 72 EXTENSION(KHR, PUSH_DESCRIPTOR, push_descriptor) \
73 EXTENSION(KHR, SAMPLER_MIRROR_CLAMP_TO_EDGE, sampler_mirror_clamp_to_edge) \ 73 EXTENSION(KHR, SAMPLER_MIRROR_CLAMP_TO_EDGE, sampler_mirror_clamp_to_edge) \
74 EXTENSION(KHR, SHADER_FLOAT_CONTROLS, shader_float_controls) \ 74 EXTENSION(KHR, SHADER_FLOAT_CONTROLS, shader_float_controls) \
@@ -80,9 +80,6 @@
80 EXTENSION(NV, VIEWPORT_ARRAY2, viewport_array2) \ 80 EXTENSION(NV, VIEWPORT_ARRAY2, viewport_array2) \
81 EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) 81 EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle)
82 82
83#define FOR_EACH_VK_EXTENSION_WIN32(EXTENSION) \
84 EXTENSION(KHR, EXTERNAL_MEMORY_WIN32, external_memory_win32)
85
86// Define extensions which must be supported. 83// Define extensions which must be supported.
87#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \ 84#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \
88 EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \ 85 EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \
@@ -90,12 +87,6 @@
90 EXTENSION_NAME(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME) \ 87 EXTENSION_NAME(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME) \
91 EXTENSION_NAME(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME) 88 EXTENSION_NAME(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME)
92 89
93#define FOR_EACH_VK_MANDATORY_EXTENSION_GENERIC(EXTENSION_NAME) \
94 EXTENSION_NAME(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME)
95
96#define FOR_EACH_VK_MANDATORY_EXTENSION_WIN32(EXTENSION_NAME) \
97 EXTENSION_NAME(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME)
98
99// Define extensions where the absence of the extension may result in a degraded experience. 90// Define extensions where the absence of the extension may result in a degraded experience.
100#define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \ 91#define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \
101 EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \ 92 EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \
@@ -528,6 +519,11 @@ public:
528 return has_renderdoc || has_nsight_graphics || Settings::values.renderer_debug.GetValue(); 519 return has_renderdoc || has_nsight_graphics || Settings::values.renderer_debug.GetValue();
529 } 520 }
530 521
522 /// @returns True if compute pipelines can cause crashing.
523 bool HasBrokenCompute() const {
524 return has_broken_compute;
525 }
526
531 /// Returns true when the device does not properly support cube compatibility. 527 /// Returns true when the device does not properly support cube compatibility.
532 bool HasBrokenCubeImageCompability() const { 528 bool HasBrokenCubeImageCompability() const {
533 return has_broken_cube_compatibility; 529 return has_broken_cube_compatibility;
@@ -589,6 +585,22 @@ public:
589 return supports_conditional_barriers; 585 return supports_conditional_barriers;
590 } 586 }
591 587
588 [[nodiscard]] static constexpr bool CheckBrokenCompute(VkDriverId driver_id,
589 u32 driver_version) {
590 if (driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
591 const u32 major = VK_API_VERSION_MAJOR(driver_version);
592 const u32 minor = VK_API_VERSION_MINOR(driver_version);
593 const u32 patch = VK_API_VERSION_PATCH(driver_version);
594 if (major == 0 && minor == 405 && patch < 286) {
595 LOG_WARNING(
596 Render_Vulkan,
597 "Intel proprietary drivers 0.405.0 until 0.405.286 have broken compute");
598 return true;
599 }
600 }
601 return false;
602 }
603
592private: 604private:
593 /// Checks if the physical device is suitable and configures the object state 605 /// Checks if the physical device is suitable and configures the object state
594 /// with all necessary info about its properties. 606 /// with all necessary info about its properties.
@@ -636,7 +648,6 @@ private:
636 FOR_EACH_VK_FEATURE_1_3(FEATURE); 648 FOR_EACH_VK_FEATURE_1_3(FEATURE);
637 FOR_EACH_VK_FEATURE_EXT(FEATURE); 649 FOR_EACH_VK_FEATURE_EXT(FEATURE);
638 FOR_EACH_VK_EXTENSION(EXTENSION); 650 FOR_EACH_VK_EXTENSION(EXTENSION);
639 FOR_EACH_VK_EXTENSION_WIN32(EXTENSION);
640 651
641#undef EXTENSION 652#undef EXTENSION
642#undef FEATURE 653#undef FEATURE
@@ -683,6 +694,7 @@ private:
683 bool is_integrated{}; ///< Is GPU an iGPU. 694 bool is_integrated{}; ///< Is GPU an iGPU.
684 bool is_virtual{}; ///< Is GPU a virtual GPU. 695 bool is_virtual{}; ///< Is GPU a virtual GPU.
685 bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. 696 bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device.
697 bool has_broken_compute{}; ///< Compute shaders can cause crashes
686 bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit 698 bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit
687 bool has_renderdoc{}; ///< Has RenderDoc attached 699 bool has_renderdoc{}; ///< Has RenderDoc attached
688 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached 700 bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 84d9ca796..733c296e4 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -210,6 +210,8 @@ add_executable(yuzu
210 util/url_request_interceptor.h 210 util/url_request_interceptor.h
211 util/util.cpp 211 util/util.cpp
212 util/util.h 212 util/util.h
213 vk_device_info.cpp
214 vk_device_info.h
213 compatdb.cpp 215 compatdb.cpp
214 compatdb.h 216 compatdb.h
215 yuzu.qrc 217 yuzu.qrc
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 8e76a819a..bdf83ebfe 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -6,6 +6,7 @@
6#include "common/settings.h" 6#include "common/settings.h"
7#include "core/core.h" 7#include "core/core.h"
8#include "ui_configure.h" 8#include "ui_configure.h"
9#include "vk_device_info.h"
9#include "yuzu/configuration/config.h" 10#include "yuzu/configuration/config.h"
10#include "yuzu/configuration/configure_audio.h" 11#include "yuzu/configuration/configure_audio.h"
11#include "yuzu/configuration/configure_cpu.h" 12#include "yuzu/configuration/configure_cpu.h"
@@ -28,6 +29,7 @@
28 29
29ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, 30ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
30 InputCommon::InputSubsystem* input_subsystem, 31 InputCommon::InputSubsystem* input_subsystem,
32 std::vector<VkDeviceInfo::Record>& vk_device_records,
31 Core::System& system_, bool enable_web_config) 33 Core::System& system_, bool enable_web_config)
32 : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, 34 : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
33 registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_, 35 registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_,
@@ -38,7 +40,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
38 general_tab{std::make_unique<ConfigureGeneral>(system_, this)}, 40 general_tab{std::make_unique<ConfigureGeneral>(system_, this)},
39 graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)}, 41 graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)},
40 graphics_tab{std::make_unique<ConfigureGraphics>( 42 graphics_tab{std::make_unique<ConfigureGraphics>(
41 system_, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this)}, 43 system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); },
44 this)},
42 hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, 45 hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)},
43 input_tab{std::make_unique<ConfigureInput>(system_, this)}, 46 input_tab{std::make_unique<ConfigureInput>(system_, this)},
44 network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, 47 network_tab{std::make_unique<ConfigureNetwork>(system_, this)},
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index a086a07c4..2a08b7fee 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -4,7 +4,9 @@
4#pragma once 4#pragma once
5 5
6#include <memory> 6#include <memory>
7#include <vector>
7#include <QDialog> 8#include <QDialog>
9#include "yuzu/vk_device_info.h"
8 10
9namespace Core { 11namespace Core {
10class System; 12class System;
@@ -40,8 +42,9 @@ class ConfigureDialog : public QDialog {
40 42
41public: 43public:
42 explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, 44 explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
43 InputCommon::InputSubsystem* input_subsystem, Core::System& system_, 45 InputCommon::InputSubsystem* input_subsystem,
44 bool enable_web_config = true); 46 std::vector<VkDeviceInfo::Record>& vk_device_records,
47 Core::System& system_, bool enable_web_config = true);
45 ~ConfigureDialog() override; 48 ~ConfigureDialog() override;
46 49
47 void ApplyConfiguration(); 50 void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 431585216..a4965524a 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -1,10 +1,6 @@
1// SPDX-FileCopyrightText: 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4// Include this early to include Vulkan headers how we want to
5#include "video_core/vulkan_common/vulkan_device.h"
6#include "video_core/vulkan_common/vulkan_wrapper.h"
7
8#include <algorithm> 4#include <algorithm>
9#include <functional> 5#include <functional>
10#include <iosfwd> 6#include <iosfwd>
@@ -34,13 +30,11 @@
34#include "common/settings.h" 30#include "common/settings.h"
35#include "core/core.h" 31#include "core/core.h"
36#include "ui_configure_graphics.h" 32#include "ui_configure_graphics.h"
37#include "video_core/vulkan_common/vulkan_instance.h"
38#include "video_core/vulkan_common/vulkan_library.h"
39#include "video_core/vulkan_common/vulkan_surface.h"
40#include "yuzu/configuration/configuration_shared.h" 33#include "yuzu/configuration/configuration_shared.h"
41#include "yuzu/configuration/configure_graphics.h" 34#include "yuzu/configuration/configure_graphics.h"
42#include "yuzu/qt_common.h" 35#include "yuzu/qt_common.h"
43#include "yuzu/uisettings.h" 36#include "yuzu/uisettings.h"
37#include "yuzu/vk_device_info.h"
44 38
45static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, 39static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR,
46 VK_PRESENT_MODE_FIFO_KHR}; 40 VK_PRESENT_MODE_FIFO_KHR};
@@ -77,9 +71,10 @@ static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode)
77} 71}
78 72
79ConfigureGraphics::ConfigureGraphics(const Core::System& system_, 73ConfigureGraphics::ConfigureGraphics(const Core::System& system_,
74 std::vector<VkDeviceInfo::Record>& records_,
80 const std::function<void()>& expose_compute_option_, 75 const std::function<void()>& expose_compute_option_,
81 QWidget* parent) 76 QWidget* parent)
82 : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, 77 : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, records{records_},
83 expose_compute_option{expose_compute_option_}, system{system_} { 78 expose_compute_option{expose_compute_option_}, system{system_} {
84 vulkan_device = Settings::values.vulkan_device.GetValue(); 79 vulkan_device = Settings::values.vulkan_device.GetValue();
85 RetrieveVulkanDevices(); 80 RetrieveVulkanDevices();
@@ -504,47 +499,19 @@ void ConfigureGraphics::UpdateAPILayout() {
504 } 499 }
505} 500}
506 501
507void ConfigureGraphics::RetrieveVulkanDevices() try { 502void ConfigureGraphics::RetrieveVulkanDevices() {
508 if (UISettings::values.has_broken_vulkan) {
509 return;
510 }
511
512 using namespace Vulkan;
513
514 auto* window = this->window()->windowHandle();
515 auto wsi = QtCommon::GetWindowSystemInfo(window);
516
517 vk::InstanceDispatch dld;
518 const auto library = OpenLibrary();
519 const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type);
520 const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices();
521 vk::SurfaceKHR surface = CreateSurface(instance, wsi);
522
523 vulkan_devices.clear(); 503 vulkan_devices.clear();
524 vulkan_devices.reserve(physical_devices.size()); 504 vulkan_devices.reserve(records.size());
525 device_present_modes.clear(); 505 device_present_modes.clear();
526 device_present_modes.reserve(physical_devices.size()); 506 device_present_modes.reserve(records.size());
527 for (const VkPhysicalDevice device : physical_devices) { 507 for (const auto& record : records) {
528 const auto physical_device = vk::PhysicalDevice(device, dld); 508 vulkan_devices.push_back(QString::fromStdString(record.name));
529 const std::string name = physical_device.GetProperties().deviceName; 509 device_present_modes.push_back(record.vsync_support);
530 const std::vector<VkPresentModeKHR> present_modes = 510
531 physical_device.GetSurfacePresentModesKHR(*surface); 511 if (record.has_broken_compute) {
532 vulkan_devices.push_back(QString::fromStdString(name));
533 device_present_modes.push_back(present_modes);
534
535 VkPhysicalDeviceDriverProperties driver_properties{};
536 driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
537 driver_properties.pNext = nullptr;
538 VkPhysicalDeviceProperties2 properties{};
539 properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
540 properties.pNext = &driver_properties;
541 dld.vkGetPhysicalDeviceProperties2(physical_device, &properties);
542 if (driver_properties.driverID == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
543 expose_compute_option(); 512 expose_compute_option();
544 } 513 }
545 } 514 }
546} catch (const Vulkan::vk::Exception& exception) {
547 LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
548} 515}
549 516
550Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { 517Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 364b1cac2..be9310b74 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -12,6 +12,7 @@
12#include <qobjectdefs.h> 12#include <qobjectdefs.h>
13#include <vulkan/vulkan_core.h> 13#include <vulkan/vulkan_core.h>
14#include "common/common_types.h" 14#include "common/common_types.h"
15#include "vk_device_info.h"
15 16
16class QEvent; 17class QEvent;
17class QObject; 18class QObject;
@@ -39,6 +40,7 @@ class ConfigureGraphics : public QWidget {
39 40
40public: 41public:
41 explicit ConfigureGraphics(const Core::System& system_, 42 explicit ConfigureGraphics(const Core::System& system_,
43 std::vector<VkDeviceInfo::Record>& records,
42 const std::function<void()>& expose_compute_option_, 44 const std::function<void()>& expose_compute_option_,
43 QWidget* parent = nullptr); 45 QWidget* parent = nullptr);
44 ~ConfigureGraphics() override; 46 ~ConfigureGraphics() override;
@@ -77,6 +79,7 @@ private:
77 ConfigurationShared::CheckState use_disk_shader_cache; 79 ConfigurationShared::CheckState use_disk_shader_cache;
78 ConfigurationShared::CheckState use_asynchronous_gpu_emulation; 80 ConfigurationShared::CheckState use_asynchronous_gpu_emulation;
79 81
82 std::vector<VkDeviceInfo::Record>& records;
80 std::vector<QString> vulkan_devices; 83 std::vector<QString> vulkan_devices;
81 std::vector<std::vector<VkPresentModeKHR>> device_present_modes; 84 std::vector<std::vector<VkPresentModeKHR>> device_present_modes;
82 std::vector<VkPresentModeKHR> 85 std::vector<VkPresentModeKHR>
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index 7ac162586..eb96e6068 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -6,6 +6,7 @@
6#include <memory> 6#include <memory>
7#include <string> 7#include <string>
8#include <utility> 8#include <utility>
9#include <vector>
9 10
10#include <fmt/format.h> 11#include <fmt/format.h>
11 12
@@ -34,8 +35,10 @@
34#include "yuzu/configuration/configure_system.h" 35#include "yuzu/configuration/configure_system.h"
35#include "yuzu/uisettings.h" 36#include "yuzu/uisettings.h"
36#include "yuzu/util/util.h" 37#include "yuzu/util/util.h"
38#include "yuzu/vk_device_info.h"
37 39
38ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, 40ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
41 std::vector<VkDeviceInfo::Record>& vk_device_records,
39 Core::System& system_) 42 Core::System& system_)
40 : QDialog(parent), 43 : QDialog(parent),
41 ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} { 44 ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} {
@@ -50,7 +53,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
50 general_tab = std::make_unique<ConfigureGeneral>(system_, this); 53 general_tab = std::make_unique<ConfigureGeneral>(system_, this);
51 graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this); 54 graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this);
52 graphics_tab = std::make_unique<ConfigureGraphics>( 55 graphics_tab = std::make_unique<ConfigureGraphics>(
53 system_, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this); 56 system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this);
54 input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this); 57 input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this);
55 system_tab = std::make_unique<ConfigureSystem>(system_, this); 58 system_tab = std::make_unique<ConfigureSystem>(system_, this);
56 59
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index 85752f1fa..7ec1ded06 100644
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -5,11 +5,13 @@
5 5
6#include <memory> 6#include <memory>
7#include <string> 7#include <string>
8#include <vector>
8 9
9#include <QDialog> 10#include <QDialog>
10#include <QList> 11#include <QList>
11 12
12#include "core/file_sys/vfs_types.h" 13#include "core/file_sys/vfs_types.h"
14#include "vk_device_info.h"
13#include "yuzu/configuration/config.h" 15#include "yuzu/configuration/config.h"
14 16
15namespace Core { 17namespace Core {
@@ -45,6 +47,7 @@ class ConfigurePerGame : public QDialog {
45public: 47public:
46 // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263 48 // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263
47 explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, 49 explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
50 std::vector<VkDeviceInfo::Record>& vk_device_records,
48 Core::System& system_); 51 Core::System& system_);
49 ~ConfigurePerGame() override; 52 ~ConfigurePerGame() override;
50 53
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 013715b44..8768a7c3c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -147,6 +147,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
147#include "yuzu/startup_checks.h" 147#include "yuzu/startup_checks.h"
148#include "yuzu/uisettings.h" 148#include "yuzu/uisettings.h"
149#include "yuzu/util/clickable_label.h" 149#include "yuzu/util/clickable_label.h"
150#include "yuzu/vk_device_info.h"
150 151
151#ifdef YUZU_DBGHELP 152#ifdef YUZU_DBGHELP
152#include "yuzu/mini_dump.h" 153#include "yuzu/mini_dump.h"
@@ -440,6 +441,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
440 441
441 renderer_status_button->setDisabled(true); 442 renderer_status_button->setDisabled(true);
442 renderer_status_button->setChecked(false); 443 renderer_status_button->setChecked(false);
444 } else {
445 VkDeviceInfo::PopulateRecords(vk_device_records, this->window()->windowHandle());
443 } 446 }
444 447
445#if defined(HAVE_SDL2) && !defined(_WIN32) 448#if defined(HAVE_SDL2) && !defined(_WIN32)
@@ -3494,7 +3497,8 @@ void GMainWindow::OnConfigure() {
3494 const auto old_language_index = Settings::values.language_index.GetValue(); 3497 const auto old_language_index = Settings::values.language_index.GetValue();
3495 3498
3496 Settings::SetConfiguringGlobal(true); 3499 Settings::SetConfiguringGlobal(true);
3497 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system, 3500 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(),
3501 vk_device_records, *system,
3498 !multiplayer_state->IsHostingPublicRoom()); 3502 !multiplayer_state->IsHostingPublicRoom());
3499 connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, 3503 connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
3500 &GMainWindow::OnLanguageChanged); 3504 &GMainWindow::OnLanguageChanged);
@@ -3765,7 +3769,7 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
3765 const auto v_file = Core::GetGameFileFromPath(vfs, file_name); 3769 const auto v_file = Core::GetGameFileFromPath(vfs, file_name);
3766 3770
3767 Settings::SetConfiguringGlobal(false); 3771 Settings::SetConfiguringGlobal(false);
3768 ConfigurePerGame dialog(this, title_id, file_name, *system); 3772 ConfigurePerGame dialog(this, title_id, file_name, vk_device_records, *system);
3769 dialog.LoadFromFile(v_file); 3773 dialog.LoadFromFile(v_file);
3770 const auto result = dialog.exec(); 3774 const auto result = dialog.exec();
3771 3775
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 6bb70972f..e0e775d87 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -118,6 +118,10 @@ enum class ReinitializeKeyBehavior {
118 Warning, 118 Warning,
119}; 119};
120 120
121namespace VkDeviceInfo {
122class Record;
123}
124
121class GMainWindow : public QMainWindow { 125class GMainWindow : public QMainWindow {
122 Q_OBJECT 126 Q_OBJECT
123 127
@@ -418,6 +422,8 @@ private:
418 422
419 GameListPlaceholder* game_list_placeholder; 423 GameListPlaceholder* game_list_placeholder;
420 424
425 std::vector<VkDeviceInfo::Record> vk_device_records;
426
421 // Status bar elements 427 // Status bar elements
422 QLabel* message_label = nullptr; 428 QLabel* message_label = nullptr;
423 QLabel* shader_building_label = nullptr; 429 QLabel* shader_building_label = nullptr;
diff --git a/src/yuzu/vk_device_info.cpp b/src/yuzu/vk_device_info.cpp
new file mode 100644
index 000000000..7c26a3dc7
--- /dev/null
+++ b/src/yuzu/vk_device_info.cpp
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <utility>
5#include <vector>
6#include "common/dynamic_library.h"
7#include "common/logging/log.h"
8#include "video_core/vulkan_common/vulkan_device.h"
9#include "video_core/vulkan_common/vulkan_instance.h"
10#include "video_core/vulkan_common/vulkan_library.h"
11#include "video_core/vulkan_common/vulkan_surface.h"
12#include "video_core/vulkan_common/vulkan_wrapper.h"
13#include "vulkan/vulkan_core.h"
14#include "yuzu/qt_common.h"
15#include "yuzu/vk_device_info.h"
16
17class QWindow;
18
19namespace VkDeviceInfo {
20Record::Record(std::string_view name_, const std::vector<VkPresentModeKHR>& vsync_modes_,
21 bool has_broken_compute_)
22 : name{name_}, vsync_support{vsync_modes_}, has_broken_compute{has_broken_compute_} {}
23
24Record::~Record() = default;
25
26void PopulateRecords(std::vector<Record>& records, QWindow* window) try {
27 using namespace Vulkan;
28
29 auto wsi = QtCommon::GetWindowSystemInfo(window);
30
31 vk::InstanceDispatch dld;
32 const auto library = OpenLibrary();
33 const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type);
34 const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices();
35 vk::SurfaceKHR surface = CreateSurface(instance, wsi);
36
37 records.clear();
38 records.reserve(physical_devices.size());
39 for (const VkPhysicalDevice device : physical_devices) {
40 const auto physical_device = vk::PhysicalDevice(device, dld);
41 const std::string name = physical_device.GetProperties().deviceName;
42 const std::vector<VkPresentModeKHR> present_modes =
43 physical_device.GetSurfacePresentModesKHR(*surface);
44
45 VkPhysicalDeviceDriverProperties driver_properties{};
46 driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
47 driver_properties.pNext = nullptr;
48 VkPhysicalDeviceProperties2 properties{};
49 properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
50 properties.pNext = &driver_properties;
51 dld.vkGetPhysicalDeviceProperties2(physical_device, &properties);
52
53 bool has_broken_compute{Vulkan::Device::CheckBrokenCompute(
54 driver_properties.driverID, properties.properties.driverVersion)};
55
56 records.push_back(VkDeviceInfo::Record(name, present_modes, has_broken_compute));
57 }
58} catch (const Vulkan::vk::Exception& exception) {
59 LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
60}
61} // namespace VkDeviceInfo
diff --git a/src/yuzu/vk_device_info.h b/src/yuzu/vk_device_info.h
new file mode 100644
index 000000000..bda8262f4
--- /dev/null
+++ b/src/yuzu/vk_device_info.h
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <algorithm>
7#include <iterator>
8#include <memory>
9#include <string>
10#include <string_view>
11#include <vector>
12#include "common/common_types.h"
13#include "vulkan/vulkan_core.h"
14
15class QWindow;
16
17namespace Settings {
18enum class VSyncMode : u32;
19}
20// #include "common/settings.h"
21
22namespace VkDeviceInfo {
23// Short class to record Vulkan driver information for configuration purposes
24class Record {
25public:
26 explicit Record(std::string_view name, const std::vector<VkPresentModeKHR>& vsync_modes,
27 bool has_broken_compute);
28 ~Record();
29
30 const std::string name;
31 const std::vector<VkPresentModeKHR> vsync_support;
32 const bool has_broken_compute;
33};
34
35void PopulateRecords(std::vector<Record>& records, QWindow* window);
36} // namespace VkDeviceInfo
diff --git a/vcpkg.json b/vcpkg.json
index 26f545c6c..7d9e631a1 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -1,7 +1,7 @@
1{ 1{
2 "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", 2 "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
3 "name": "yuzu", 3 "name": "yuzu",
4 "builtin-baseline": "656fcc6ab2b05c6d999b7eaca717027ac3738f71", 4 "builtin-baseline": "cbf56573a987527b39272e88cbdd11389b78c6e4",
5 "version": "1.0", 5 "version": "1.0",
6 "dependencies": [ 6 "dependencies": [
7 "boost-algorithm", 7 "boost-algorithm",