diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81b406770..0d02dd2f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: name: ${{ env.OS }}-${{ env.TARGET }} path: artifacts/ macos: - runs-on: ${{ (matrix.target == 'x86_64' && 'macos-13') || 'macos-14' }} + runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }} strategy: fail-fast: false matrix: @@ -103,7 +103,7 @@ jobs: name: ${{ env.OS }}-${{ env.TARGET }} path: artifacts/ macos-universal: - runs-on: macos-14 + runs-on: macos-26 needs: macos env: OS: macos diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a736eaac..9c5e5c14e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,15 @@ if (APPLE) else() # Minimum macOS 13 set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4") + + # Catch compiler issue on AppleClang versions below 15.0 + # TODO: Remove this check when we drop macOS 13 Ventura + if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND + CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15.0) + message(FATAL_ERROR "AppleClang 15.0 or later is required due to a compiler bug in earlier versions.\n" + "Current version: ${CMAKE_CXX_COMPILER_VERSION}\n" + "After updating, delete 'CMakeCache.txt' in the build directory.") + endif() endif() endif() @@ -112,6 +121,8 @@ option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF) option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON) +option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF) + # Compile options CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF) option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO}) @@ -299,7 +310,7 @@ find_package(Threads REQUIRED) if (ENABLE_QT) if (NOT USE_SYSTEM_QT) - download_qt(6.7.2) + download_qt(6.9.2) endif() find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent) diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake index fc8ec6089..a76d95ae1 100644 --- a/CMakeModules/DownloadExternals.cmake +++ b/CMakeModules/DownloadExternals.cmake @@ -20,9 +20,9 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out set(arch_path "mingw_64") elseif (MSVC) if ("arm64" IN_LIST ARCHITECTURE) - set(arch_path "msvc2019_arm64") + set(arch_path "msvc2022_arm64") elseif ("x86_64" IN_LIST ARCHITECTURE) - set(arch_path "msvc2019_64") + set(arch_path "msvc2022_64") else() message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.") endif() @@ -30,12 +30,13 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out # In case we're cross-compiling, prepare to also fetch the correct host Qt tools. if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64") - set(host_arch_path "msvc2019_64") + set(host_arch_path "msvc2022_64") elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64") # TODO: msvc2019_arm64 doesn't include some of the required tools for some reason, # TODO: so until it does, just use msvc2019_64 under x86_64 emulation. + # TODO: ^ Is this still true with msvc2022? # set(host_arch_path "msvc2019_arm64") - set(host_arch_path "msvc2019_64") + set(host_arch_path "msvc2022_64") endif() set(host_arch "win64_${host_arch_path}") else() @@ -105,7 +106,7 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba if (NOT EXISTS "${prefix}") message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}") - set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.18") + set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.3.0") if (WIN32) set(aqt_path "${base_path}/aqt.exe") if (NOT EXISTS "${aqt_path}") diff --git a/dist/apple/Info.plist.in b/dist/apple/Info.plist.in index d12a21f8e..2feacd1b6 100644 --- a/dist/apple/Info.plist.in +++ b/dist/apple/Info.plist.in @@ -75,6 +75,9 @@ NSHighResolutionCapable True + UIDesignRequiresCompatibility + UIFileSharingEnabled UILaunchStoryboardName diff --git a/dist/compatibility_list b/dist/compatibility_list index 4f3904169..a36decbe4 160000 --- a/dist/compatibility_list +++ b/dist/compatibility_list @@ -1 +1 @@ -Subproject commit 4f39041699412873d0afcec89a9313148a192647 +Subproject commit a36decbe43d0e5a570ac3d3ba9a0b226dc832a17 diff --git a/dist/languages/ca_ES_valencia.ts b/dist/languages/ca_ES_valencia.ts index 65ef0ca2a..bc138e02f 100644 --- a/dist/languages/ca_ES_valencia.ts +++ b/dist/languages/ca_ES_valencia.ts @@ -27,7 +27,7 @@ <html><head/><body><p><img src=":/icons/default/256x256/azahar.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/default/256x256/azahar.png"/></p></body></html> @@ -725,7 +725,7 @@ Desitja ignorar l'error i continuar? Show log output in console - + Mostrar l'eixida del registre en la consola @@ -2275,7 +2275,7 @@ Desitja ignorar l'error i continuar? <a href='https://web.archive.org/web/20240301211230/https://citra-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> - + <a href='https://web.archive.org/web/20240301211230/https://citra-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Més Informació</span></a> @@ -2494,12 +2494,12 @@ Desitja ignorar l'error i continuar? Compress installed CIA content - + Comprimir el contingut de CIAs instal·lats Compresses the content of CIA files when installed to the emulated SD card. Only affects CIA content which is installed while the setting is enabled. - + Comprimix el contingut de fitxers CIA quan són instal·lats a la SD emulada. Només afecta contingut CIA instal·lat amb esta opció activada. @@ -4431,7 +4431,7 @@ Vols reinstal·lar els arxius de totes maneres? 3DS Installation File (*.cia *.zcia) - + Fitxers d'Instalació de 3DS (*.cia *.zcia) @@ -4510,24 +4510,24 @@ Vols reinstal·lar els arxius de totes maneres? Error compressing file - + Error al comprimir el fitxer File compress operation failed, check log for details. - + Operació de compressió fallida, mira el registre per a més detalls. Error decompressing file - + Error de descompressió del fitxer File decompress operation failed, check log for details. - + Operació de descompressió fallida, mira el registre per a més detalls. @@ -4663,62 +4663,62 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda. Load 3DS ROM File - + Carregar ROM de 3DS 3DS ROM Files (*.cia *cci *3dsx *cxi) - + Fitxers ROM 3DS (*.cia *cci *3dsx *cxi) The selected file is not a compatible 3DS ROM format. Make sure you have chosen the right file, and that it is not encrypted. - + El fitxer seleccionat no és un ROM de 3DS compatible. Assegura't que has triat el fitxer correcte i que no estiga xifrat. The selected file is already compressed. - + El fitxer seleccionat ja està comprimit. 3DS Compressed ROM File (*.%1) - + Fitxer ROM 3DS comprimit (*.%1) Save 3DS Compressed ROM File - + Desar fitxer 3DS comprimit Load 3DS Compressed ROM File - + Carregar fitxer 3DS comprimit 3DS Compressed ROM Files (*.zcia *zcci *z3dsx *zcxi) - + Fitxer ROM 3DS comprimit (*.zcia *zcci *z3dsx *zcxi) The selected file is not a compatible compressed 3DS ROM format. Make sure you have chosen the right file. - + El fitxer seleccionat no és un format de ROM 3DS comprimit compatible. Assegura't d'haver triat l'arxiu correcte. The selected file is already decompressed. - + El fitxer seleccionat ja està descomprimit. 3DS ROM File (*.%1) - + Fitxer ROM 3DS (*.%1) Save 3DS ROM File - + Desar fitxer ROM 3DS @@ -4818,7 +4818,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda. Frame: %1 ms (GPU: [CMD: %2 ms, SWP: %3 ms], IPC: %4 ms, SVC: %5 ms, Rem: %6 ms) - + Frame: %1 ms (GPU: [CMD: %2 ms, SWP: %3 ms], IPC: %4 ms, SVC: %5 ms, Rem: %6 ms) @@ -4839,7 +4839,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda. %1 is missing. Please <a href='https://web.archive.org/web/20240304201103/https://citra-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>dump your system archives</a>.<br/>Continuing emulation may result in crashes and bugs. - + Falta %1 . Per favor,<a href='https://web.archive.org/web/20240304201103/https://citra-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>bolca els teus arxius de sistema</a>.<br/>Continuar l'emulació pot resultar en penges i errors. @@ -4869,7 +4869,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda. A fatal error occurred. <a href='https://web.archive.org/web/20240228001712/https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Check the log</a> for details.<br/>Continuing emulation may result in crashes and bugs. - + Error fatal.<a href='https://web.archive.org/web/20240228001712/https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Mira el log</a>per a més detalls.<br/>Continuar l'emulació pot resultar en penges i errors. @@ -6387,12 +6387,12 @@ Missatge de depuració: Compress ROM File... - + Comprimir fitxer ROM... Decompress ROM File... - + Descomprimir fitxer ROM... diff --git a/dist/languages/es_ES.ts b/dist/languages/es_ES.ts index b872896e8..a8126d7c9 100644 --- a/dist/languages/es_ES.ts +++ b/dist/languages/es_ES.ts @@ -4517,7 +4517,7 @@ Reinstall the files anyway? File compress operation failed, check log for details. - Operación de comprensión fallida, mira el registro para más detalles. + Operación de compresión fallida, mira el registro para más detalles. @@ -4529,7 +4529,7 @@ Reinstall the files anyway? File decompress operation failed, check log for details. - Operación de descomprensión fallida, mira el registro para más detalles. + Operación de descompresión fallida, mira el registro para más detalles. diff --git a/dist/languages/ru_RU.ts b/dist/languages/ru_RU.ts index f5a4a9b3c..1dcb9ad96 100644 --- a/dist/languages/ru_RU.ts +++ b/dist/languages/ru_RU.ts @@ -3894,7 +3894,7 @@ Drag points to change position, or double-click table cells to edit values. Show current application in your Discord status - + Показывать текущее приложение в статусе Discord @@ -4027,7 +4027,7 @@ Drag points to change position, or double-click table cells to edit values. Azahar - Azahar + Azahar @@ -4170,7 +4170,7 @@ Please check your FFmpeg installation used for compilation. GBA Virtual Console is not supported by Azahar. - + Azahar не поддерживает GBA Virtual Console. @@ -4315,7 +4315,7 @@ Please check your FFmpeg installation used for compilation. Azahar - Azahar + Azahar @@ -4523,7 +4523,7 @@ Reinstall the files anyway? Error decompressing file - + Ошибра при разжатии файла @@ -5036,7 +5036,7 @@ Would you like to download it? Don't show again - + Не показывать снова @@ -7383,7 +7383,8 @@ They may have left the room. Azahar has detected user data for Citra. - + Azahar обнаружил файлы пользователя Citra. + diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 66cbbd52e..aa6e6c84b 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -1,24 +1,37 @@ # Definitions for all external bundled libraries # Suppress warnings from external libraries -if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") +if (MSVC) add_compile_options(/W0) else() add_compile_options(-w) endif() +function(target_disable_warnings target) + if (MSVC) + target_compile_options(${target} INTERFACE /W0) + else() + target_compile_options(${target} INTERFACE -w) + endif() +endfunction() + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) include(DownloadExternals) include(ExternalProject) # Boost -if (NOT USE_SYSTEM_BOOST) +if (USE_SYSTEM_BOOST) + unset(BOOST_ROOT CACHE) + unset(Boost_INCLUDE_DIR CACHE) + set(Boost_NO_SYSTEM_PATHS OFF CACHE BOOL "" FORCE) +else() message(STATUS "Including vendored Boost library") set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "") set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "") set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "") add_library(boost INTERFACE) - target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR}) + target_include_directories(boost INTERFACE ${Boost_INCLUDE_DIR}) + target_disable_warnings(boost) # Boost::serialization file(GLOB boost_serialization_SRC "${CMAKE_SOURCE_DIR}/externals/boost/libs/serialization/src/*.cpp") @@ -33,11 +46,7 @@ if (NOT USE_SYSTEM_BOOST) ${CMAKE_SOURCE_DIR}/externals/boost/libs/iostreams/src/mapped_file.cpp ) target_link_libraries(boost_iostreams PUBLIC boost) -# Add additional boost libs here; remember to ALIAS them in the root CMakeLists! -else() - unset(BOOST_ROOT CACHE) - unset(Boost_INCLUDE_DIR CACHE) - set(Boost_NO_SYSTEM_PATHS OFF CACHE BOOL "" FORCE) + # Add additional boost libs here; remember to ALIAS them in the root CMakeLists! endif() # Catch2 @@ -73,6 +82,7 @@ endif() # dds-ktx add_library(dds-ktx INTERFACE) target_include_directories(dds-ktx INTERFACE ./dds-ktx) +target_disable_warnings(dds-ktx) # fmt and Xbyak need to be added before dynarmic # libfmt @@ -137,7 +147,8 @@ endif() # MicroProfile add_library(microprofile INTERFACE) -target_include_directories(microprofile SYSTEM INTERFACE ./microprofile) +target_include_directories(microprofile INTERFACE ./microprofile) +target_disable_warnings(microprofile) if (ENABLE_MICROPROFILE) target_compile_definitions(microprofile INTERFACE MICROPROFILE_ENABLED=1) else() @@ -146,10 +157,11 @@ endif() # Nihstro add_library(nihstro-headers INTERFACE) -target_include_directories(nihstro-headers SYSTEM INTERFACE ./nihstro/include) -if (MSVC) - # TODO: For some reason MSVC still applies this warning even with /W0 for externals. - target_compile_options(nihstro-headers INTERFACE /wd4715) +target_include_directories(nihstro-headers INTERFACE ./nihstro/include) +target_disable_warnings(nihstro-headers) +if (NOT MSVC) + # TODO: For some reason MSYS2 still applied this warnin even with -w + target_compile_options(nihstro-headers INTERFACE -Wno-invalid-specialization) endif() # Open Source Archives @@ -173,7 +185,8 @@ if (USE_SYSTEM_FFMPEG_HEADERS) endif() if (NOT FOUND_FFMPEG_HEADERS) message(STATUS "Using bundled ffmpeg headers.") - target_include_directories(library-headers SYSTEM INTERFACE ./library-headers/ffmpeg/include) + target_include_directories(library-headers INTERFACE ./library-headers/ffmpeg/include) + target_disable_warnings(library-headers) endif() # SoundTouch @@ -294,7 +307,8 @@ if (USE_SYSTEM_JSON) # Citra uses "#include " so we have to add this manually target_include_directories(json-headers SYSTEM INTERFACE "${NLOHMANN_PREFIX}/nlohmann") else() - target_include_directories(json-headers SYSTEM INTERFACE ./json) + target_include_directories(json-headers INTERFACE ./json) + target_disable_warnings(json-headers) endif() # OpenSSL @@ -310,7 +324,8 @@ if (NOT OPENSSL_FOUND) set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "") set(OPENSSLDIR "/etc/ssl/") add_subdirectory(libressl EXCLUDE_FROM_ALL) - target_include_directories(ssl SYSTEM INTERFACE ./libressl/include) + target_include_directories(ssl INTERFACE ./libressl/include) + target_disable_warnings(ssl) target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP) get_directory_property(OPENSSL_LIBRARIES DIRECTORY libressl @@ -327,17 +342,20 @@ if(USE_SYSTEM_CPP_HTTPLIB) get_target_property(HTTP_LIBS httplib::httplib INTERFACE_LINK_LIBRARIES) if(HTTP_LIBS) message(WARNING "Shared cpp-http (${HTTP_LIBS}) not supported. Falling back to bundled...") - target_include_directories(httplib SYSTEM INTERFACE ./httplib) + target_include_directories(httplib INTERFACE ./httplib) + target_disable_warnings(httplib) else() if(CppHttp_FOUND) target_link_libraries(httplib INTERFACE httplib::httplib) else() message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...") - target_include_directories(httplib SYSTEM INTERFACE ./httplib) - endif() + target_include_directories(httplib INTERFACE ./httplib) + target_disable_warnings(httplib) + endif() endif() else() - target_include_directories(httplib SYSTEM INTERFACE ./httplib) + target_include_directories(httplib INTERFACE ./httplib) + target_disable_warnings(httplib) endif() target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT) target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES}) @@ -354,7 +372,8 @@ if (ENABLE_WEB_SERVICE) target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt) else() add_library(cpp-jwt INTERFACE) - target_include_directories(cpp-jwt SYSTEM INTERFACE ./cpp-jwt/include) + target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include) + target_disable_warnings(cpp-jwt) target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON) endif() endif() @@ -453,7 +472,8 @@ if (ENABLE_VULKAN) endif() else() add_library(vma INTERFACE) - target_include_directories(vma SYSTEM INTERFACE ./vma/include) + target_include_directories(vma INTERFACE ./vma/include) + target_disable_warnings(vma) endif() # vulkan-headers @@ -465,7 +485,8 @@ if (ENABLE_VULKAN) target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers) endif() else() - target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include) + target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include) + target_disable_warnings(vulkan-headers) endif() # adrenotools diff --git a/externals/fmt b/externals/fmt index 123913715..e424e3f2e 160000 --- a/externals/fmt +++ b/externals/fmt @@ -1 +1 @@ -Subproject commit 123913715afeb8a437e6388b4473fcc4753e1c9a +Subproject commit e424e3f2e607da02742f73db84873b8084fc714c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 43ab20d46..69d17bac2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -174,6 +174,9 @@ endif() if(ENABLE_VULKAN) add_compile_definitions(ENABLE_VULKAN) endif() +if(ENABLE_DEVELOPER_OPTIONS) + add_compile_definitions(ENABLE_DEVELOPER_OPTIONS) +endif() add_subdirectory(common) add_subdirectory(core) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 84006116f..10e766459 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -64,7 +64,7 @@ android { // The application ID refers to Lime3DS to allow for // the Play Store listing, which was originally set up for Lime3DS, to still be used. applicationId = "io.github.lime3ds.android" - minSdk = 28 + minSdk = 29 targetSdk = 35 versionCode = autoVersion versionName = getGitVersion() @@ -186,7 +186,7 @@ dependencies { // Download Vulkan Validation Layers from the KhronosGroup GitHub. val downloadVulkanValidationLayers = tasks.register("downloadVulkanValidationLayers") { - src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.304.1/android-binaries-1.4.304.1.zip") + src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip") dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip")) onlyIfModified(true) } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index eec388463..e53354dc9 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -96,6 +96,24 @@ object NativeLibrary { */ external fun onTouchMoved(xAxis: Float, yAxis: Float) + /** + * Handles touch events on the secondary display. + * + * @param xAxis The value of the x-axis. + * @param yAxis The value of the y-axis. + * @param pressed To identify if the touch held down or released. + * @return true if the pointer is within the touchscreen + */ + external fun onSecondaryTouchEvent(xAxis: Float, yAxis: Float, pressed: Boolean): Boolean + + /** + * Handles touch movement on the secondary display. + * + * @param xAxis The value of the instantaneous x-axis. + * @param yAxis The value of the instantaneous y-axis. + */ + external fun onSecondaryTouchMoved(xAxis: Float, yAxis: Float) + external fun reloadSettings() external fun getTitleId(filename: String): Long diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 3ff594ce9..f23147dd8 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -60,7 +60,15 @@ class EmulationActivity : AppCompatActivity() { private lateinit var binding: ActivityEmulationBinding private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil private lateinit var hotkeyUtility: HotkeyUtility - private lateinit var secondaryDisplay: SecondaryDisplay; + private lateinit var secondaryDisplay: SecondaryDisplay + + private val onShutdown = Runnable { + if (intent.getBooleanExtra("launched_from_shortcut", false)) { + finishAffinity() + } else { + this.finish() + } + } private val emulationFragment: EmulationFragment get() { @@ -77,8 +85,8 @@ class EmulationActivity : AppCompatActivity() { ThemeUtil.setTheme(this) settingsViewModel.settings.loadSettings() super.onCreate(savedInstanceState) - secondaryDisplay = SecondaryDisplay(this); - secondaryDisplay.updateDisplay(); + secondaryDisplay = SecondaryDisplay(this) + secondaryDisplay.updateDisplay() binding = ActivityEmulationBinding.inflate(layoutInflater) screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings) @@ -101,13 +109,7 @@ class EmulationActivity : AppCompatActivity() { windowManager.defaultDisplay.rotation ) - EmulationLifecycleUtil.addShutdownHook(hook = { - if (intent.getBooleanExtra("launched_from_shortcut", false)) { - finishAffinity() - } else { - this.finish() - } - }) + EmulationLifecycleUtil.addShutdownHook(onShutdown) isEmulationRunning = true instance = this @@ -165,12 +167,12 @@ class EmulationActivity : AppCompatActivity() { } override fun onDestroy() { - EmulationLifecycleUtil.clear() + EmulationLifecycleUtil.removeHook(onShutdown) NativeLibrary.playTimeManagerStop() isEmulationRunning = false instance = null secondaryDisplay.releasePresentation() - secondaryDisplay.releaseVD(); + secondaryDisplay.releaseVD() super.onDestroy() } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt index 4af42792c..b44929c9d 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt @@ -11,29 +11,30 @@ import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.os.Bundle import android.view.Display +import android.view.MotionEvent import android.view.Surface import android.view.SurfaceHolder import android.view.SurfaceView -import org.citra.citra_emu.NativeLibrary +import android.view.WindowManager import org.citra.citra_emu.features.settings.model.IntSetting +import org.citra.citra_emu.display.SecondaryDisplayLayout +import org.citra.citra_emu.NativeLibrary -class SecondaryDisplay(val context: Context) { +class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener { private var pres: SecondaryDisplayPresentation? = null private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager private val vd: VirtualDisplay init { - val st = SurfaceTexture(0) - st.setDefaultBufferSize(1920, 1080) - val vdSurface = Surface(st) vd = displayManager.createVirtualDisplay( "HiddenDisplay", 1920, 1080, 320, - vdSurface, + null, DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION ) + displayManager.registerDisplayListener(this, null) } fun updateSurface() { @@ -44,16 +45,23 @@ class SecondaryDisplay(val context: Context) { NativeLibrary.secondarySurfaceDestroyed() } + private fun getExternalDisplay(context: Context): Display? { + val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + val internalId = context.display.displayId ?: Display.DEFAULT_DISPLAY + val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION) + return displays.firstOrNull { it.displayId != internalId && it.name != "HiddenDisplay" } + } + fun updateDisplay() { // decide if we are going to the external display or the internal one - var display = getCustomerDisplay() + var display = getExternalDisplay(context) if (display == null || IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) { display = vd.display } // if our presentation is already on the right display, ignore - if (pres?.display == display) return; + if (pres?.display == display) return // otherwise, make a new presentation releasePresentation() @@ -61,29 +69,41 @@ class SecondaryDisplay(val context: Context) { pres?.show() } - private fun getCustomerDisplay(): Display? { - val displays = displayManager.displays - // code taken from MelonDS dual screen - should fix odin 2 detection bug - return displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION) - .firstOrNull { it.displayId != Display.DEFAULT_DISPLAY && it.name != "Built-in Screen" && it.name != "HiddenDisplay"} - } - fun releasePresentation() { pres?.dismiss() pres = null } fun releaseVD() { + displayManager.unregisterDisplayListener(this) vd.release() } + + override fun onDisplayAdded(displayId: Int) { + updateDisplay() + } + + override fun onDisplayRemoved(displayId: Int) { + updateDisplay() + } + override fun onDisplayChanged(displayId: Int) { + updateDisplay() + } } class SecondaryDisplayPresentation( context: Context, display: Display, val parent: SecondaryDisplay ) : Presentation(context, display) { private lateinit var surfaceView: SurfaceView + private var touchscreenPointerId = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + window?.setFlags( + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + ) // Initialize SurfaceView surfaceView = SurfaceView(context) @@ -103,6 +123,42 @@ class SecondaryDisplayPresentation( } }) + this.surfaceView.setOnTouchListener { _, event -> + + val pointerIndex = event.actionIndex + val pointerId = event.getPointerId(pointerIndex) + when (event.actionMasked) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { + if (touchscreenPointerId == -1) { + touchscreenPointerId = pointerId + NativeLibrary.onSecondaryTouchEvent( + event.getX(pointerIndex), + event.getY(pointerIndex), + true + ) + } + } + + MotionEvent.ACTION_MOVE -> { + val index = event.findPointerIndex(touchscreenPointerId) + if (index != -1) { + NativeLibrary.onSecondaryTouchMoved( + event.getX(index), + event.getY(index) + ) + } + } + + MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> { + if (pointerId == touchscreenPointerId) { + NativeLibrary.onSecondaryTouchEvent(0f, 0f, false) + touchscreenPointerId = -1 + } + } + } + true + } + setContentView(surfaceView) // Set SurfaceView as content } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt index 925e17805..11f55d184 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt @@ -19,13 +19,14 @@ enum class BooleanSetting( INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false), ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false), CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false), - OVERLAY_SHOW_FPS("overlay_show_fps", Settings.SECTION_LAYOUT, true), - OVERLAY_SHOW_FRAMETIME("overlay_show_frame_time", Settings.SECTION_LAYOUT, false), - OVERLAY_SHOW_SPEED("overlay_show_speed", Settings.SECTION_LAYOUT, false), - OVERLAY_SHOW_APP_RAM_USAGE("overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false), - OVERLAY_SHOW_AVAILABLE_RAM("overlay_show_available_ram", Settings.SECTION_LAYOUT, false), - OVERLAY_SHOW_BATTERY_TEMP("overlay_show_battery_temp", Settings.SECTION_LAYOUT, false), - OVERLAY_BACKGROUND("overlay_background", Settings.SECTION_LAYOUT, false), + PERF_OVERLAY_ENABLE("performance_overlay_enable", Settings.SECTION_LAYOUT, false), + PERF_OVERLAY_SHOW_FPS("performance_overlay_show_fps", Settings.SECTION_LAYOUT, true), + PERF_OVERLAY_SHOW_FRAMETIME("performance_overlay_show_frame_time", Settings.SECTION_LAYOUT, false), + PERF_OVERLAY_SHOW_SPEED("performance_overlay_show_speed", Settings.SECTION_LAYOUT, false), + PERF_OVERLAY_SHOW_APP_RAM_USAGE("performance_overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false), + PERF_OVERLAY_SHOW_AVAILABLE_RAM("performance_overlay_show_available_ram", Settings.SECTION_LAYOUT, false), + PERF_OVERLAY_SHOW_BATTERY_TEMP("performance_overlay_show_battery_temp", Settings.SECTION_LAYOUT, false), + PERF_OVERLAY_BACKGROUND("performance_overlay_background", Settings.SECTION_LAYOUT, false), DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true), DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false), REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false), @@ -49,7 +50,8 @@ enum class BooleanSetting( DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, false), USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, false), UPRIGHT_SCREEN("upright_screen", Settings.SECTION_LAYOUT, false), - COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false); + COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false), + ANDROID_HIDE_IMAGES("android_hide_images", Settings.SECTION_CORE, false); override var boolean: Boolean = defaultValue @@ -83,6 +85,8 @@ enum class BooleanSetting( SHADERS_ACCURATE_MUL, USE_ARTIC_BASE_CONTROLLER, COMPRESS_INSTALLED_CIA_CONTENT, + ANDROID_HIDE_IMAGES, + PERF_OVERLAY_ENABLE // Works in overlay options, but not from the settings menu ) fun from(key: String): BooleanSetting? = diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.kt index 94ddb295d..69bc16ca7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.kt @@ -1,4 +1,4 @@ -// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -10,6 +10,10 @@ enum class FloatSetting( override val defaultValue: Float ) : AbstractFloatSetting { LARGE_SCREEN_PROPORTION("large_screen_proportion",Settings.SECTION_LAYOUT,2.25f), + SECOND_SCREEN_OPACITY("custom_second_layer_opacity", Settings.SECTION_RENDERER, 100f), + BACKGROUND_RED("bg_red", Settings.SECTION_RENDERER, 0f), + BACKGROUND_BLUE("bg_blue", Settings.SECTION_RENDERER, 0f), + BACKGROUND_GREEN("bg_green", Settings.SECTION_RENDERER, 0f), EMPTY_SETTING("", "", 0.0f); override var float: Float = defaultValue diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.kt index 79406594e..46ed42905 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.kt @@ -1,4 +1,4 @@ -// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -10,7 +10,6 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.FloatSetting import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.utils.Log -import kotlin.math.roundToInt class SliderSetting( setting: AbstractSetting?, @@ -27,7 +26,8 @@ class SliderSetting( val selectedFloat: Float get() { val setting = setting ?: return defaultValue!!.toFloat() - return when (setting) { + + val ret = when (setting) { is AbstractIntSetting -> setting.int.toFloat() is FloatSetting -> setting.float is ScaledFloatSetting -> setting.float @@ -36,8 +36,8 @@ class SliderSetting( -1f } } + return ret.coerceIn(min.toFloat(), max.toFloat()) } - /** * Write a value to the backing int. If that int was previously null, * initializes a new one and returns it, so it can be added to the Hashmap. diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivityPresenter.kt index 06fcb19e4..33aea46f9 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivityPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivityPresenter.kt @@ -4,14 +4,19 @@ package org.citra.citra_emu.features.settings.ui +import android.net.Uri import android.os.Bundle import android.text.TextUtils +import androidx.documentfile.provider.DocumentFile +import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.NativeLibrary -import org.citra.citra_emu.features.settings.model.IntSetting +import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.utils.SystemSaveGame import org.citra.citra_emu.utils.DirectoryInitialization +import org.citra.citra_emu.utils.FileUtil import org.citra.citra_emu.utils.Log +import org.citra.citra_emu.utils.PermissionsHandler import org.citra.citra_emu.utils.TurboHelper class SettingsActivityPresenter(private val activityView: SettingsActivityView) { @@ -60,6 +65,32 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) loadSettingsUI() } + private fun updateAndroidImageVisibility() { + val dataDirTreeUri: Uri + val dataDirDocument: DocumentFile + val nomediaFileDocument: DocumentFile? + val nomediaFileExists: Boolean + try { + dataDirTreeUri = PermissionsHandler.citraDirectory + dataDirDocument = DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!! + nomediaFileDocument = dataDirDocument.findFile(".nomedia") + nomediaFileExists = (nomediaFileDocument != null) + } catch (e: Exception) { + Log.error("[SettingsActivity]: Error occurred while trying to find .nomedia, error: " + e.message) + return + } + + if (BooleanSetting.ANDROID_HIDE_IMAGES.boolean) { + if (!nomediaFileExists) { + Log.info("[SettingsActivity]: Attempting to create .nomedia in user data directory") + FileUtil.createFile(dataDirTreeUri.toString(), ".nomedia") + } + } else if (nomediaFileExists) { + Log.info("[SettingsActivity]: Attempting to delete .nomedia in user data directory") + nomediaFileDocument!!.delete() + } + } + fun onStop(finishing: Boolean) { if (finishing && shouldSave) { Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") @@ -67,6 +98,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) //added to ensure that layout changes take effect as soon as settings window closes NativeLibrary.reloadSettings() NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode) + updateAndroidImageVisibility() TurboHelper.reloadTurbo(false) // TODO: Can this go somewhere else? -OS } NativeLibrary.reloadSettings() diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 28fb82e84..d4baf6166 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -248,6 +248,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.TURBO_LIMIT.defaultValue.toFloat() ) ) + add( + SwitchSetting( + BooleanSetting.ANDROID_HIDE_IMAGES, + R.string.android_hide_images, + R.string.android_hide_images_description, + BooleanSetting.ANDROID_HIDE_IMAGES.key, + BooleanSetting.ANDROID_HIDE_IMAGES.defaultValue + ) + ) } } @@ -1166,6 +1175,89 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) FloatSetting.LARGE_SCREEN_PROPORTION.defaultValue ) ) + add( + SliderSetting( + FloatSetting.SECOND_SCREEN_OPACITY, + R.string.second_screen_opacity, + R.string.second_screen_opacity_description, + 0, + 100, + "%", + FloatSetting.SECOND_SCREEN_OPACITY.key, + FloatSetting.SECOND_SCREEN_OPACITY.defaultValue, + isEnabled = IntSetting.SCREEN_LAYOUT.int == 5 + ) + ) + add(HeaderSetting(R.string.bg_color, R.string.bg_color_description)) + val bgRedSetting = object : AbstractIntSetting { + override var int: Int + get() = (FloatSetting.BACKGROUND_RED.float * 255).toInt() + set(value) { + FloatSetting.BACKGROUND_RED.float = value.toFloat() / 255 + settings.saveSetting(FloatSetting.BACKGROUND_RED, SettingsFile.FILE_NAME_CONFIG) + } + override val key = null + override val section = null + override val isRuntimeEditable = false + override val valueAsString = int.toString() + override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue + } + add( + SliderSetting( + bgRedSetting, + R.string.bg_red, + 0, + 0, + 255, + "" + ) + ) + val bgGreenSetting = object : AbstractIntSetting { + override var int: Int + get() = (FloatSetting.BACKGROUND_GREEN.float * 255).toInt() + set(value) { + FloatSetting.BACKGROUND_GREEN.float = value.toFloat() / 255 + settings.saveSetting(FloatSetting.BACKGROUND_GREEN, SettingsFile.FILE_NAME_CONFIG) + } + override val key = null + override val section = null + override val isRuntimeEditable = false + override val valueAsString = int.toString() + override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue + } + add( + SliderSetting( + bgGreenSetting, + R.string.bg_green, + 0, + 0, + 255, + "" + ) + ) + val bgBlueSetting = object : AbstractIntSetting { + override var int: Int + get() = (FloatSetting.BACKGROUND_BLUE.float * 255).toInt() + set(value) { + FloatSetting.BACKGROUND_BLUE.float = value.toFloat() / 255 + settings.saveSetting(FloatSetting.BACKGROUND_BLUE, SettingsFile.FILE_NAME_CONFIG) + } + override val key = null + override val section = null + override val isRuntimeEditable = false + override val valueAsString = int.toString() + override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue + } + add( + SliderSetting( + bgBlueSetting, + R.string.bg_blue, + 0, + 0, + 255, + "" + ) + ) add( SubmenuSetting( R.string.performance_overlay_options, @@ -1201,38 +1293,29 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( - object : AbstractBooleanSetting { - override val key = "EmulationMenuSettings_showPerfPerformanceOverlay" - override val section = Settings.SECTION_LAYOUT - override val defaultValue = false - override var boolean: Boolean - get() = EmulationMenuSettings.showPerformanceOverlay - set(value) { EmulationMenuSettings.showPerformanceOverlay = value } - override val isRuntimeEditable = true - override val valueAsString: String get() = boolean.toString() - }, + BooleanSetting.PERF_OVERLAY_ENABLE, R.string.performance_overlay_enable, 0, - "EmulationMenuSettings_showPerfPerformanceOverlay", - false + BooleanSetting.PERF_OVERLAY_ENABLE.key, + BooleanSetting.PERF_OVERLAY_ENABLE.defaultValue ) ) add( SwitchSetting( - BooleanSetting.OVERLAY_BACKGROUND, - R.string.overlay_background, - R.string.overlay_background_description, - "overlay_background", - false + BooleanSetting.PERF_OVERLAY_BACKGROUND, + R.string.performance_overlay_background, + R.string.performance_overlay_background_description, + BooleanSetting.PERF_OVERLAY_BACKGROUND.key, + BooleanSetting.PERF_OVERLAY_BACKGROUND.defaultValue ) ) add( SingleChoiceSetting( IntSetting.PERFORMANCE_OVERLAY_POSITION, - R.string.overlay_position, - R.string.overlay_position_description, + R.string.performance_overlay_position, + R.string.performance_overlay_position_description, R.array.statsPosition, R.array.statsPositionValues, ) @@ -1243,61 +1326,61 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( - BooleanSetting.OVERLAY_SHOW_FPS, - R.string.overlay_show_fps, - R.string.overlay_show_fps_description, - "overlay_show_fps", - true + BooleanSetting.PERF_OVERLAY_SHOW_FPS, + R.string.performance_overlay_show_fps, + R.string.performance_overlay_show_fps_description, + BooleanSetting.PERF_OVERLAY_SHOW_FPS.key, + BooleanSetting.PERF_OVERLAY_SHOW_FPS.defaultValue ) ) add( SwitchSetting( - BooleanSetting.OVERLAY_SHOW_FRAMETIME, - R.string.overlay_show_frametime, - R.string.overlay_show_frametime_description, - "overlay_show_frame_time", - true + BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME, + R.string.performance_overlay_show_frametime, + R.string.performance_overlay_show_frametime_description, + BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME.key, + BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME.defaultValue ) ) add( SwitchSetting( - BooleanSetting.OVERLAY_SHOW_SPEED, - R.string.overlay_show_speed, - R.string.overlay_show_speed_description, - "overlay_show_speed", - false + BooleanSetting.PERF_OVERLAY_SHOW_SPEED, + R.string.performance_overlay_show_speed, + R.string.performance_overlay_show_speed_description, + BooleanSetting.PERF_OVERLAY_SHOW_SPEED.key, + BooleanSetting.PERF_OVERLAY_SHOW_SPEED.defaultValue ) ) add( SwitchSetting( - BooleanSetting.OVERLAY_SHOW_APP_RAM_USAGE, - R.string.overlay_show_app_ram_usage, - R.string.overlay_show_app_ram_usage_description, - "overlay_show_app_ram_usage", - false + BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE, + R.string.performance_overlay_show_app_ram_usage, + R.string.performance_overlay_show_app_ram_usage_description, + BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.key, + BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.defaultValue ) ) add( SwitchSetting( - BooleanSetting.OVERLAY_SHOW_AVAILABLE_RAM, - R.string.overlay_show_available_ram, - R.string.overlay_show_available_ram_description, - "overlay_show_available_ram", - false + BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM, + R.string.performance_overlay_show_available_ram, + R.string.performance_overlay_show_available_ram_description, + BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM.key, + BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM.defaultValue ) ) add( SwitchSetting( - BooleanSetting.OVERLAY_SHOW_BATTERY_TEMP, - R.string.overlay_show_battery_temp, - R.string.overlay_show_battery_temp_description, - "overlay_show_battery_temp", - false + BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP, + R.string.performance_overlay_show_battery_temp, + R.string.performance_overlay_show_battery_temp_description, + BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP.key, + BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP.defaultValue ) ) } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index fbf3e5525..419919527 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -66,6 +66,7 @@ import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.display.ScreenLayout import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.IntSetting +import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.ui.SettingsActivity import org.citra.citra_emu.features.settings.utils.SettingsFile @@ -100,6 +101,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private val emulationViewModel: EmulationViewModel by activityViewModels() private val settingsViewModel: SettingsViewModel by viewModels() + private val settings get() = settingsViewModel.settings + + private val onPause = Runnable{ togglePause() } + private val onShutdown = Runnable{ emulationState.stop() } override fun onAttach(context: Context) { super.onAttach(context) @@ -155,9 +160,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram retainInstance = true emulationState = EmulationState(game.path) emulationActivity = requireActivity() as EmulationActivity - screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings) - EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) - EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) + screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settings) + EmulationLifecycleUtil.addPauseResumeHook(onPause) + EmulationLifecycleUtil.addShutdownHook(onShutdown) } override fun onCreateView( @@ -507,6 +512,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram super.onDetach() } + override fun onDestroy() { + EmulationLifecycleUtil.removeHook(onPause) + EmulationLifecycleUtil.removeHook(onShutdown) + super.onDestroy() + } + private fun setupCitraDirectoriesThenStartEmulation() { val directoryInitializationState = DirectoryInitialization.start() if (directoryInitializationState === @@ -662,7 +673,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.menu.apply { findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay findItem(R.id.menu_performance_overlay_show).isChecked = - EmulationMenuSettings.showPerformanceOverlay + BooleanSetting.PERF_OVERLAY_ENABLE.boolean findItem(R.id.menu_haptic_feedback).isChecked = EmulationMenuSettings.hapticFeedback findItem(R.id.menu_emulation_joystick_rel_center).isChecked = EmulationMenuSettings.joystickRelCenter @@ -679,7 +690,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } R.id.menu_performance_overlay_show -> { - EmulationMenuSettings.showPerformanceOverlay = !EmulationMenuSettings.showPerformanceOverlay + BooleanSetting.PERF_OVERLAY_ENABLE.boolean = !BooleanSetting.PERF_OVERLAY_ENABLE.boolean + settings.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, SettingsFile.FILE_NAME_CONFIG) updateShowPerformanceOverlay() true } @@ -1202,7 +1214,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) } - if (EmulationMenuSettings.showPerformanceOverlay) { + if (BooleanSetting.PERF_OVERLAY_ENABLE.boolean) { val SYSTEM_FPS = 0 val FPS = 1 val SPEED = 2 @@ -1217,11 +1229,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram val perfStats = NativeLibrary.getPerfStats() val dividerString = "\u00A0\u2502 " if (perfStats[FPS] > 0) { - if (BooleanSetting.OVERLAY_SHOW_FPS.boolean) { + if (BooleanSetting.PERF_OVERLAY_SHOW_FPS.boolean) { sb.append(String.format("FPS:\u00A0%d", (perfStats[FPS] + 0.5).toInt())) } - if (BooleanSetting.OVERLAY_SHOW_FRAMETIME.boolean) { + if (BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME.boolean) { if (sb.isNotEmpty()) sb.append(dividerString) sb.append( String.format( @@ -1236,7 +1248,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram ) } - if (BooleanSetting.OVERLAY_SHOW_SPEED.boolean) { + if (BooleanSetting.PERF_OVERLAY_SHOW_SPEED.boolean) { if (sb.isNotEmpty()) sb.append(dividerString) sb.append( String.format( @@ -1246,14 +1258,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram ) } - if (BooleanSetting.OVERLAY_SHOW_APP_RAM_USAGE.boolean) { + if (BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.boolean) { if (sb.isNotEmpty()) sb.append(dividerString) val appRamUsage = File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000 sb.append("Process\u00A0RAM:\u00A0$appRamUsage\u00A0MB") } - if (BooleanSetting.OVERLAY_SHOW_AVAILABLE_RAM.boolean) { + if (BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM.boolean) { if (sb.isNotEmpty()) sb.append(dividerString) context?.let { ctx -> val activityManager = @@ -1266,14 +1278,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } } - if (BooleanSetting.OVERLAY_SHOW_BATTERY_TEMP.boolean) { + if (BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP.boolean) { if (sb.isNotEmpty()) sb.append(dividerString) val batteryTemp = getBatteryTemperature() val tempF = celsiusToFahrenheit(batteryTemp) sb.append(String.format("%.1f°C/%.1f°F", batteryTemp, tempF)) } - if (BooleanSetting.OVERLAY_BACKGROUND.boolean) { + if (BooleanSetting.PERF_OVERLAY_BACKGROUND.boolean) { binding.performanceOverlayShowText.setBackgroundResource(R.color.citra_transparent_black) } else { binding.performanceOverlayShowText.setBackgroundResource(0) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt index 9828dd046..f7519bb81 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt @@ -153,8 +153,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex if (isActionMove) { NativeLibrary.onTouchMoved(xPosition.toFloat(), yPosition.toFloat()) continue - } - else if (isActionUp) { + } else if (isActionUp) { NativeLibrary.onTouchEvent(0f, 0f, false) break // Up and down actions shouldn't loop } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt index 8f3b5dc07..5fafb7bed 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -18,15 +18,27 @@ object EmulationLifecycleUtil { } fun addShutdownHook(hook: Runnable) { - shutdownHooks.add(hook) + if (shutdownHooks.contains(hook)) { + Log.warning("[EmulationLifecycleUtil] Tried to add shutdown hook for function that already existed. Skipping.") + } else { + shutdownHooks.add(hook) + } } fun addPauseResumeHook(hook: Runnable) { - pauseResumeHooks.add(hook) + if (pauseResumeHooks.contains(hook)) { + Log.warning("[EmulationLifecycleUtil] Tried to add pause resume hook for function that already existed. Skipping.") + } else { + pauseResumeHooks.add(hook) + } } - fun clear() { - pauseResumeHooks.clear() - shutdownHooks.clear() + fun removeHook(hook: Runnable) { + if (pauseResumeHooks.contains(hook)) { + pauseResumeHooks.remove(hook) + } + if (shutdownHooks.contains(hook)) { + shutdownHooks.remove(hook) + } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt index 184964549..6ff08fa37 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt @@ -35,13 +35,6 @@ object EmulationMenuSettings { .apply() } - var showPerformanceOverlay: Boolean - get() = preferences.getBoolean("EmulationMenuSettings_showPerformanceOverlay", false) - set(value) { - preferences.edit() - .putBoolean("EmulationMenuSettings_showPerformanceOverlay", value) - .apply() - } var hapticFeedback: Boolean get() = preferences.getBoolean("EmulationMenuSettings_HapticFeedback", true) set(value) { diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 1f2e8708b..9ac9f3737 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -172,6 +172,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.bg_red); ReadSetting("Renderer", Settings::values.bg_green); ReadSetting("Renderer", Settings::values.bg_blue); + ReadSetting("Renderer", Settings::values.custom_second_layer_opacity); ReadSetting("Renderer", Settings::values.delay_game_render_thread_us); ReadSetting("Renderer", Settings::values.disable_right_eye_render); diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 46083be50..5bedfa319 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -170,6 +170,9 @@ bg_red = bg_blue = bg_green = +# Opacity of second layer when using custom layout option (bottom screen unless swapped). Useful if positioning on top of the first layer. +custom_second_layer_opacity = + # Whether and how Stereoscopic 3D should be rendered # 0 (default): Off, 1: Side by Side, 2: Reverse Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced, 6: Cardboard VR render_3d = diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 2cb34cbd0..a2880d6dc 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -657,6 +657,25 @@ void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEn window->OnTouchMoved((int)x, (int)y); } +jboolean Java_org_citra_citra_1emu_NativeLibrary_onSecondaryTouchEvent([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jobject obj, + jfloat x, jfloat y, + jboolean pressed) { + if (!secondary_window) { + return JNI_FALSE; + } + return static_cast(secondary_window->OnTouchEvent( + static_cast(x + 0.5), static_cast(y + 0.5), pressed)); +} + +void Java_org_citra_citra_1emu_NativeLibrary_onSecondaryTouchMoved([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jobject obj, + jfloat x, jfloat y) { + if (secondary_window) { + secondary_window->OnTouchMoved((int)x, (int)y); + } +} + jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_unused]] jobject obj, jstring j_filename) { std::string filepath = GetJString(env, j_filename); diff --git a/src/android/app/src/main/res/values-b+ca+ES+valencia/strings.xml b/src/android/app/src/main/res/values-b+ca+ES+valencia/strings.xml index eb3beaa25..0f0e3e978 100644 --- a/src/android/app/src/main/res/values-b+ca+ES+valencia/strings.xml +++ b/src/android/app/src/main/res/values-b+ca+ES+valencia/strings.xml @@ -193,6 +193,10 @@ Advertiment Regió No Vàlida La configuració del país no és vàlida per a la regió emulada seleccionada. La configuració del país no és vàlida per a la consola vinculada actual. + Emmagatzematge + Comprimir el contingut de CIAs instal·lats + Comprimix el contingut de fitxers CIA quan són instal·lats a la SD emulada. Només afecta contingut CIA instal·lat amb esta opció activada. + Càmera interior Càmera esquerra externa @@ -397,6 +401,10 @@ S\'esperen errors gràfics temporals quan estigue activat. Configurar Controls Editar Estil Fet + Lliscament de botons + Mantindre el botó pressionat originalment + Mantindre el botó pressionat actualment + Mantindre el botó original i actualment pressionat Activar Controls Ajustar Escala Escala Global @@ -409,6 +417,8 @@ S\'esperen errors gràfics temporals quan estigue activat. Relació d\'Aspecte Estil de Pantalla Apaïsada Estil de Pantalla de Perfil + Estil de Pantalla Secundària + La disposició de la pantalla secundària connectada, amb cable o sense fil (Chromecast, Miracast) Pantalla amplia Vertical Pantalla Única @@ -416,6 +426,7 @@ S\'esperen errors gràfics temporals quan estigue activat. Pantalles híbrides Original Per omissió + Per defecte del sistema (espill) Estil Personalitzat Posició de Pantalla Xicoteta On hauria d\'aparéixer la pantalla xicoteta en relació amb la gran en Proporció de Pantalla Gran? @@ -536,6 +547,10 @@ S\'esperen errors gràfics temporals quan estigue activat. Crear drecera El nom de la drecera no pot estar buit Allargar per a ajustar la imatge + ID: + Fitxer: + Tipus: + Mostrar informació de rendiment Informació de rendiment diff --git a/src/android/app/src/main/res/values-b+es+ES/strings.xml b/src/android/app/src/main/res/values-b+es+ES/strings.xml index d0c9496bf..f2232f9f9 100644 --- a/src/android/app/src/main/res/values-b+es+ES/strings.xml +++ b/src/android/app/src/main/res/values-b+es+ES/strings.xml @@ -194,7 +194,7 @@ La configuración del país no es válida para la región emulada seleccionada. La configuración del país no es válida para la consola vinculada actual. Almacenamiento - Comprimir el contenido CIA instalado + Comprimir el contenido de CIAs instalados Comprime el contenido de archivos CIA cuando son instalados a la SD emulada. Solo afecta contenido CIA instalado con esta opción activada. @@ -402,9 +402,9 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Editar Estilo Hecho Deslizamiento de botones - Mantenga el botón presionado originalmente - Mantenga el botón presionado actualmente - Mantenga el botón original y actualmente presionado + Mantener el botón presionado originalmente + Mantener el botón presionado actualmente + Mantener el botón original y actualmente presionado Activar Controles Ajustar Escala Escala Global @@ -417,6 +417,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Relación de Aspecto Estilo de Pantalla Apaisada Estilo de Pantalla de Perfil + Estilo de Pantalla Secundaria + El estilo de la pantalla secundaria conectada, con cable o inalámbrica (Chromecast, Miracast) Pantalla amplia Retrato Pantalla Única @@ -424,6 +426,7 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Pantallas híbridas Original Por defecto + Por defecto del sistema (espejo) Estilo personalizado Posición Pantalla Pequeña ¿Dónde debería aparecer la pantalla pequeña en relación con la grande en Disposicion de Pantalla Grande? diff --git a/src/android/app/src/main/res/values-b+pl+PL/strings.xml b/src/android/app/src/main/res/values-b+pl+PL/strings.xml index 29e2aabd2..17a1fbbca 100644 --- a/src/android/app/src/main/res/values-b+pl+PL/strings.xml +++ b/src/android/app/src/main/res/values-b+pl+PL/strings.xml @@ -417,6 +417,7 @@ Geometryczny Układ Ekranu Pionowy Układ Ekranu Układ ekranu wyświetlacza dodatkowego + Układ używany przez podłączony dodatkowy ekran, przewodowy lub bezprzewodowy (Chromecast, Miracast) Duży Ekran Ekran Pojedynczy ekran @@ -424,6 +425,7 @@ Hybrydowy Ekran Oryginalny Domyślny + Ustawienia domyślne systemu (mirror) Niestandardowy Układ Pozycja małego ekranu Gdzie powinien być wyświetlany mały ekran względem dużego w układzie dużego ekranu? diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index e29871840..17bda5e66 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -148,12 +148,12 @@ - @string/overlay_position_top_left - @string/overlay_position_center_top - @string/overlay_position_top_right - @string/overlay_position_bottom_left - @string/overlay_position_center_bottom - @string/overlay_position_bottom_right + @string/performance_overlay_position_top_left + @string/performance_overlay_position_center_top + @string/performance_overlay_position_top_right + @string/performance_overlay_position_bottom_left + @string/performance_overlay_position_center_bottom + @string/performance_overlay_position_bottom_right 0 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c79be56c8..078113f12 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -238,25 +238,27 @@ Emits the fragment shader used to emulate PICA using SPIR-V instead of GLSL Disable SPIR-V Optimizer Disables the SPIR-V optimization pass, reducing stuttering considerably while barely affecting performance. - Enable asynchronous shader compilation + Enable Asynchronous Shader Compilation Compiles shaders in the background to reduce stuttering during gameplay. When enabled expect temporary graphical glitches Linear Filtering Enables linear filtering, which causes game visuals to appear smoother. Texture Filter Enhances the visuals of applications by applying a filter to textures. The supported filters are Anime4K Ultrafast, Bicubic, ScaleForce, xBRZ freescale, and MMPX. - Delay game render thread + Delay Game Render Thread Delay the game render thread when it submits data to the GPU. Helps with performance issues in the (very few) applications with dynamic framerates. Advanced Texture Sampling Overrides the sampling filter used by games. This can be useful in certain cases with poorly behaved games when upscaling. If unsure, set this to Game Controlled. Accurate Multiplication Uses more accurate multiplication in hardware shaders, which may fix some graphical bugs. When enabled, performance will be reduced. - Enable asynchronous GPU emulation + Enable Asynchronous GPU Emulation Uses a separate thread to emulate the GPU asynchronously. When enabled, performance will be improved. Limit Speed When enabled, emulation speed will be limited to a specified percentage of normal speed. If disabled, emulation speed will be uncapped and the turbo speed hotkey will not work. Limit Speed Percent Specifies the percentage to limit emulation speed. With the default of 100% emulation will be limited to normal speed. Values higher or lower will increase or decrease the speed limit. + Hide 3DS Images from Android + Prevent 3DS camera, screenshot, and custom texture images from being indexed by Android and displayed in the gallery. Your device may need to be rebooted after changing this setting to take effect. Turbo Speed Limit Emulation speed limit used while the turbo hotkey is active. Expand to Cutout Area @@ -306,7 +308,7 @@ Volume Audio Stretching Stretches audio to reduce stuttering. When enabled, increases audio latency and slightly reduces performance. - Enable realtime audio + Enable Realtime Audio Scales audio playback speed to account for drops in emulation framerate. This means that audio will play at full speed even while the game framerate is low. May cause audio desync issues. Audio Input Device Sound Output Mode @@ -321,9 +323,9 @@ Synchronizes the game frame rate to the refresh rate of your device. Debug Renderer Log additional graphics related debug information. When enabled, game performance will be significantly reduced. - Flush log output on every message + Flush Log Output on Every Message Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut. - Delay start with LLE modules + Delay Start With LLE Modules Delays the start of the app when LLE modules are enabled. Deterministic Async Operations Makes async operations deterministic for debugging. Enabling this may cause freezes. @@ -448,6 +450,13 @@ Default System Default (mirror) Custom Layout + Background Color + The color which appears behind the screens during emulation, represented as an RGB value. + Red + Green + Blue + Custom Layout Second Screen Opacity + The opacity of the second 3DS screen when using a custom screen layout. Useful if the second screen is to be positioned on top of the first screen. Small Screen Position Where should the small screen appear relative to the large one in Large Screen Layout? Top Right @@ -576,28 +585,28 @@ Performance Overlay Enable Performance Overlay Configure whether the performance overlay is shown and what information is displayed. - Show FPS - Display current frames per second. - Show Frametime - Display current frametime. - Show Speed - Display current emulation speed percentage. - Show App Memory Usage - Display the amount of RAM getting used by the emulator. - Show Available Memory - Display the amount of RAM which is available. - Show Battery Temperature - Display current Battery temperature in Celsius and Fahrenheit. - Overlay Position - Choose where the performance overlay is displayed on the screen. - Top Left - Top Right - Bottom Left - Bottom Right - Center Top - Center Bottom - Overlay Background - Adds a background behind the overlay for easier reading. + Show FPS + Display current frames per second. + Show Frametime + Display current frametime. + Show Speed + Display current emulation speed percentage. + Show App Memory Usage + Display the amount of RAM getting used by the emulator. + Show Available Memory + Display the amount of RAM which is available. + Show Battery Temperature + Display current Battery temperature in Celsius and Fahrenheit. + Overlay Position + Choose where the performance overlay is displayed on the screen. + Top Left + Top Right + Bottom Left + Bottom Right + Center Top + Center Bottom + Overlay Background + Adds a background behind the overlay for easier reading. Cheats diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h index 65b5a820c..73a10f567 100644 --- a/src/audio_core/sink.h +++ b/src/audio_core/sink.h @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -9,7 +9,7 @@ namespace AudioCore { -constexpr char auto_device_name[] = "auto"; +constexpr char auto_device_name[] = "Auto"; /** * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed diff --git a/src/citra_meta/CMakeLists.txt b/src/citra_meta/CMakeLists.txt index 9f9dcd574..182423a2b 100644 --- a/src/citra_meta/CMakeLists.txt +++ b/src/citra_meta/CMakeLists.txt @@ -60,6 +60,10 @@ if (ENABLE_QT AND UNIX AND NOT APPLE) target_link_libraries(citra_meta PRIVATE Qt6::DBus gamemode) endif() +if (ENABLE_QT AND APPLE) + target_link_libraries(citra_meta PRIVATE Qt6::GuiPrivate) +endif() + if (ENABLE_QT AND USE_DISCORD_PRESENCE) target_link_libraries(citra_meta PRIVATE discord-rpc) endif() diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index a36d40a4d..7dc2b777b 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -172,12 +172,12 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL multiplayer/state.h multiplayer/validation.h precompiled_headers.h + qt_image_interface.cpp + qt_image_interface.h uisettings.cpp uisettings.h user_data_migration.cpp user_data_migration.h - qt_image_interface.cpp - qt_image_interface.h util/clickable_label.cpp util/clickable_label.h util/graphics_device_info.cpp @@ -190,6 +190,13 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL util/util.h ) +if (APPLE) + target_sources(citra_qt PUBLIC + qt_swizzle.h + qt_swizzle.mm + ) +endif() + file(GLOB COMPAT_LIST ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) @@ -272,7 +279,12 @@ endif() if (NOT WIN32) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) - target_include_directories(citra_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + if(Qt6_VERSION VERSION_GREATER_EQUAL "6.10.0") + find_package(Qt6 REQUIRED COMPONENTS GuiPrivate) + target_link_libraries(citra_qt PRIVATE Qt6::GuiPrivate) + else() + target_include_directories(citra_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + endif() endif() if (UNIX AND NOT APPLE) diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index bc9b85661..ea28f0f2c 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -69,6 +69,7 @@ #include "citra_qt/movie/movie_record_dialog.h" #include "citra_qt/multiplayer/state.h" #include "citra_qt/qt_image_interface.h" +#include "citra_qt/qt_swizzle.h" #include "citra_qt/uisettings.h" #include "common/play_time_manager.h" #ifdef ENABLE_QT_UPDATE_CHECKER @@ -979,6 +980,10 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList); connect(game_list, &GameList::PopulatingCompleted, this, [this] { multiplayer_state->UpdateGameList(game_list->GetModel()); }); +#ifdef ENABLE_DEVELOPER_OPTIONS + connect(game_list, &GameList::StartingLaunchStressTest, this, + &GMainWindow::StartLaunchStressTest); +#endif connect(game_list, &GameList::OpenPerGameGeneralRequested, this, &GMainWindow::OnGameListOpenPerGameProperties); @@ -1575,6 +1580,16 @@ void GMainWindow::ShutdownGame() { secondary_window->ReleaseRenderTarget(); } +void GMainWindow::StartLaunchStressTest(const QString& game_path) { + QThreadPool::globalInstance()->start([this, game_path] { + do { + ui->action_Stop->trigger(); + emit game_list->GameChosen(game_path); + QThread::sleep(2); + } while (emulation_running); + }); +} + void GMainWindow::StoreRecentFile(const QString& filename) { UISettings::values.recent_files.prepend(filename); UISettings::values.recent_files.removeDuplicates(); @@ -4112,6 +4127,11 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() { } void LaunchQtFrontend(int argc, char* argv[]) { +#ifdef __APPLE__ + // Ensure that the linker doesn't optimize qt_swizzle.mm out of existence. + QtSwizzle::Dummy(); +#endif + Common::DetachedTasks detached_tasks; #if MICROPROFILE_ENABLED diff --git a/src/citra_qt/citra_qt.h b/src/citra_qt/citra_qt.h index 70e0493f3..746689cb5 100644 --- a/src/citra_qt/citra_qt.h +++ b/src/citra_qt/citra_qt.h @@ -306,6 +306,7 @@ private slots: #endif void OnSwitchDiskResources(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); + void StartLaunchStressTest(const QString& game_path); private: Q_INVOKABLE void OnMoviePlaybackCompleted(); diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 041085eec..0e7777911 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -371,7 +371,7 @@ void QtConfig::ReadControlValues() { const auto append_profile = [this, num_touch_from_button_maps] { Settings::InputProfile profile; profile.name = - ReadSetting(QStringLiteral("name"), QStringLiteral("default")).toString().toStdString(); + ReadSetting(QStringLiteral("name"), QStringLiteral("Default")).toString().toStdString(); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); profile.buttons[i] = ReadSetting(QString::fromUtf8(Settings::NativeButton::mapping[i]), diff --git a/src/citra_qt/configuration/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui index 5bf8e3b0b..2a34765be 100644 --- a/src/citra_qt/configuration/configure_audio.ui +++ b/src/citra_qt/configuration/configure_audio.ui @@ -35,7 +35,7 @@ - Emulation: + Emulation diff --git a/src/citra_qt/configuration/configure_camera.ui b/src/citra_qt/configuration/configure_camera.ui index 07977edd4..ce39fc9d1 100644 --- a/src/citra_qt/configuration/configure_camera.ui +++ b/src/citra_qt/configuration/configure_camera.ui @@ -28,7 +28,7 @@ Select the camera to configure - Camera to configure: + Camera to Configure @@ -59,7 +59,7 @@ Select the camera mode (single or double) - Camera mode: + Camera mode @@ -90,7 +90,7 @@ Select the position of camera to configure - Camera position: + Camera position @@ -130,7 +130,7 @@ Select where the image of the emulated camera comes from. It may be an image or a real camera. - Camera Image Source: + Camera Image Source @@ -166,7 +166,7 @@ QFrame::NoFrame - File: + File @@ -193,7 +193,7 @@ QFrame::NoFrame - Camera: + Camera @@ -234,7 +234,7 @@ QFrame::NoFrame - Flip: + Flip diff --git a/src/citra_qt/configuration/configure_enhancements.ui b/src/citra_qt/configuration/configure_enhancements.ui index b69676d02..f26943444 100644 --- a/src/citra_qt/configuration/configure_enhancements.ui +++ b/src/citra_qt/configuration/configure_enhancements.ui @@ -113,7 +113,7 @@ - Enable Linear Filtering + Enable linear filtering @@ -320,7 +320,7 @@ - Disable Right Eye Rendering + Disable right eye rendering <html><head/><body><p>Disable Right Eye Rendering</p><p>Disables rendering the right eye image when not using stereoscopic mode. Greatly improves performance in some applications, but can cause flickering in others.</p></body></html> @@ -342,7 +342,7 @@ <html><head/><body><p>Replace textures with PNG files.</p><p>Textures are loaded from load/textures/[Title ID]/.</p></body></html> - Use Custom Textures + Use custom textures @@ -352,7 +352,7 @@ <html><head/><body><p>Dump textures to PNG files.</p><p>Textures are dumped to dump/textures/[Title ID]/.</p></body></html> - Dump Textures + Dump textures @@ -362,7 +362,7 @@ <html><head/><body><p>Load all custom textures into memory on boot, instead of loading them when the application requires them.</p></body></html> - Preload Custom Textures + Preload custom textures @@ -372,7 +372,7 @@ <html><head/><body><p>Load custom textures asynchronously with background threads to reduce loading stutter</p></body></html> - Async Custom Texture Loading + Async custom texture loading diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index 741b898c3..a4fee730a 100644 --- a/src/citra_qt/configuration/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui @@ -97,7 +97,7 @@ - Set emulation speed: + Set emulation speed @@ -105,7 +105,7 @@ - Emulation Speed: + Emulation Speed diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index fea3c523d..ec6a5392c 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -132,14 +132,14 @@ - SPIR-V Shader Generation + SPIR-V shader generation - Disable GLSL -> SPIR-V Optimizer + Disable GLSL -> SPIR-V optimizer <html><head/><body><p>Disables the SPIR-V optimization pass, reducing stuttering considerably while barely affecting performance.</p></body></html> @@ -176,7 +176,7 @@ <html><head/><body><p>Use the selected graphics API to accelerate shader emulation.</p><p>Requires a relatively powerful GPU for better performance.</p></body></html> - Enable Hardware Shader + Enable hardware shader @@ -201,7 +201,7 @@ <html><head/><body><p>Correctly handle all edge cases in multiplication operation in shaders. </p><p>Some applications requires this to be enabled for the hardware shader to render properly.</p><p>However this would reduce performance in most applications.</p></body></html> - Accurate Multiplication + Accurate multiplication @@ -217,7 +217,7 @@ <html><head/><body><p>Use the JIT engine instead of the interpreter for software shader emulation. </p><p>Enable this for better performance.</p></body></html> - Enable Shader JIT + Enable shader JIT @@ -227,7 +227,7 @@ <html><head/><body><p>Compile shaders using background threads to avoid shader compilation stutter. Expect temporary graphical glitches</p></body></html> - Enable Async Shader Compilation + Enable async shader compilation @@ -237,7 +237,7 @@ <html><head/><body><p>Perform presentation on separate threads. Improves performance when using Vulkan in most applications.</p></body></html> - Enable Async Presentation + Enable async presentation @@ -303,7 +303,7 @@ <html><head/><body><p>Reduce stuttering by storing and loading generated shaders to disk.</p></body></html> - Use Disk Shader Cache + Use disk shader cache @@ -349,7 +349,7 @@ - Delay application render thread: + Delay Application Render Thread <html><head/><body><p>Delays the emulated application render thread the specified amount of milliseconds every time it submits render commands to the GPU.</p><p>Adjust this feature in the (very few) dynamic framerate applications to fix performance issues.</p></body></html> diff --git a/src/citra_qt/configuration/configure_layout.ui b/src/citra_qt/configuration/configure_layout.ui index 128e146e3..1125ab2fb 100644 --- a/src/citra_qt/configuration/configure_layout.ui +++ b/src/citra_qt/configuration/configure_layout.ui @@ -132,14 +132,14 @@ - Swap Screens + Swap screens - Rotate Screens Upright + Rotate screens upright @@ -531,7 +531,7 @@ - <html><head/><body><p>Bottom Screen Opacity % (OpenGL Only)</p></body></html> + <html><head/><body><p>Bottom Screen Opacity %</p></body></html> diff --git a/src/citra_qt/configuration/configure_storage.ui b/src/citra_qt/configuration/configure_storage.ui index 1c8358dbc..54827022c 100644 --- a/src/citra_qt/configuration/configure_storage.ui +++ b/src/citra_qt/configuration/configure_storage.ui @@ -27,7 +27,7 @@ - Use Virtual SD + Use virtual SD card @@ -42,7 +42,7 @@ - Use Custom Storage + Use custom storage location diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index 244fb3c5f..080ee139a 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -92,7 +92,7 @@ online features (if installed) - Region: + Region @@ -549,7 +549,7 @@ online features (if installed) - 3GX Plugin Loader: + 3GX Plugin Loader diff --git a/src/citra_qt/configuration/configure_ui.ui b/src/citra_qt/configuration/configure_ui.ui index f522a910e..f59bc171f 100644 --- a/src/citra_qt/configuration/configure_ui.ui +++ b/src/citra_qt/configuration/configure_ui.ui @@ -37,7 +37,7 @@ - Interface language: + Interface Language @@ -51,7 +51,7 @@ - Theme: + Theme @@ -78,7 +78,7 @@ - Icon Size: + Icon Size @@ -108,7 +108,7 @@ - Row 1 Text: + Row 1 Text @@ -148,7 +148,7 @@ - Row 2 Text: + Row 2 Text @@ -191,14 +191,14 @@ - Hide Titles without Icon + Hide titles without icon - Single Line Mode + Single line mode @@ -218,7 +218,7 @@ - Show Advanced Frame Time Info + Show advanced frame time info diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 049f7579c..601bb2349 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -644,6 +644,11 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr shortcut_menu->addAction(tr("Add to Applications Menu")); #endif +#ifdef ENABLE_DEVELOPER_OPTIONS + context_menu.addSeparator(); + QAction* stress_test_launch = context_menu.addAction(tr("Stress Test: App Launch")); +#endif + context_menu.addSeparator(); QAction* properties = context_menu.addAction(tr("Properties")); @@ -755,6 +760,10 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr [this, path, program_id] { emit DumpRomFSRequested(path, program_id); }); connect(remove_play_time_data, &QAction::triggered, [this, program_id]() { emit RemovePlayTimeRequested(program_id); }); +#ifdef ENABLE_DEVELOPER_OPTIONS + connect(stress_test_launch, &QAction::triggered, + [this, path]() { emit StartingLaunchStressTest(path); }); +#endif connect(properties, &QAction::triggered, this, [this, path]() { emit OpenPerGameGeneralRequested(path); }); connect(open_shader_cache_location, &QAction::triggered, this, [this, program_id] { diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 92556bc04..d563f74b1 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -107,6 +107,7 @@ signals: void AddDirectory(); void ShowList(bool show); void PopulatingCompleted(); + void StartingLaunchStressTest(const QString& game_path); private slots: void OnItemExpanded(const QModelIndex& item); diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp index c77aac36f..f1e29b145 100644 --- a/src/citra_qt/multiplayer/direct_connect.cpp +++ b/src/citra_qt/multiplayer/direct_connect.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -90,7 +90,8 @@ void DirectConnectWindow::Connect() { room_member->Join(ui->nickname->text().toStdString(), Service::CFG::GetConsoleIdHash(system), ui->ip->text().toStdString().c_str(), port, 0, - Network::NoPreferredMac, ui->password->text().toStdString().c_str()); + Service::CFG::GetConsoleMacAddress(system), + ui->password->text().toStdString().c_str()); } }); watcher->setFuture(f); diff --git a/src/citra_qt/multiplayer/host_room.cpp b/src/citra_qt/multiplayer/host_room.cpp index 5f29eed2d..53de0071c 100644 --- a/src/citra_qt/multiplayer/host_room.cpp +++ b/src/citra_qt/multiplayer/host_room.cpp @@ -193,8 +193,8 @@ void HostRoomWindow::Host() { } #endif member->Join(ui->username->text().toStdString(), Service::CFG::GetConsoleIdHash(system), - "127.0.0.1", static_cast(port), 0, Network::NoPreferredMac, password, - token); + "127.0.0.1", static_cast(port), 0, + Service::CFG::GetConsoleMacAddress(system), password, token); // Store settings UISettings::values.room_nickname = ui->username->text(); diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp index 7a7f032c1..c18c8eefd 100644 --- a/src/citra_qt/multiplayer/lobby.cpp +++ b/src/citra_qt/multiplayer/lobby.cpp @@ -175,7 +175,8 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { #endif if (auto room_member = Network::GetRoomMember().lock()) { room_member->Join(nickname, Service::CFG::GetConsoleIdHash(system), ip.c_str(), - static_cast(port), 0, Network::NoPreferredMac, password, token); + static_cast(port), 0, Service::CFG::GetConsoleMacAddress(system), + password, token); } }); watcher->setFuture(f); diff --git a/src/citra_qt/qt_swizzle.h b/src/citra_qt/qt_swizzle.h new file mode 100644 index 000000000..25c396b79 --- /dev/null +++ b/src/citra_qt/qt_swizzle.h @@ -0,0 +1,9 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +namespace QtSwizzle { + +void Dummy(); + +} // namespace QtSwizzle diff --git a/src/citra_qt/qt_swizzle.mm b/src/citra_qt/qt_swizzle.mm new file mode 100644 index 000000000..46acdad8f --- /dev/null +++ b/src/citra_qt/qt_swizzle.mm @@ -0,0 +1,48 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#import +#import + +namespace QtSwizzle { + +void Dummy() { + // Call this anywhere to make sure that qt_swizzle.mm is linked. + // noop +} + +} // namespace QtSwizzle + +@implementation QMetalLayer (AzaharPatch) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class targetClass = [self class]; + + // Get the original and swizzled methods + Method originalMethod = + class_getInstanceMethod(targetClass, @selector(setNeedsDisplayInRect:)); + Method swizzledMethod = + class_getInstanceMethod(targetClass, @selector(swizzled_setNeedsDisplayInRect:)); + + // Swap the implementations + method_exchangeImplementations(originalMethod, swizzledMethod); + }); +} + +- (void)swizzled_setNeedsDisplayInRect:(CGRect)rect { + constexpr auto tooBig = 1e10; // Arbitrary large number + + // Check for problematic huge rectangles + if ((!self.needsDisplay) && (rect.size.width > tooBig || rect.size.height > tooBig || + rect.origin.x < -tooBig || rect.origin.y < -tooBig)) { + return; + } + + // Call the original implementation + [self swizzled_setNeedsDisplayInRect:rect]; +} + +@end \ No newline at end of file diff --git a/src/common/hacks/hack_list.cpp b/src/common/hacks/hack_list.cpp index c5702df5e..ce93ad2ef 100644 --- a/src/common/hacks/hack_list.cpp +++ b/src/common/hacks/hack_list.cpp @@ -67,6 +67,9 @@ HackManager hack_manager = { 0x0004013000002C02, // Normal 0x0004013000002C03, // Safe mode 0x0004013020002C03, // New 3DS safe mode + + // DLP + 0x0004013000002802, }, }}, diff --git a/src/common/settings.h b/src/common/settings.h index 0bb947269..77061db16 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -585,9 +585,9 @@ struct Values { SwitchableSetting enable_realtime_audio{false, "enable_realtime_audio"}; SwitchableSetting volume{1.f, 0.f, 1.f, "volume"}; Setting output_type{AudioCore::SinkType::Auto, "output_type"}; - Setting output_device{"auto", "output_device"}; + Setting output_device{"Auto", "output_device"}; Setting input_type{AudioCore::InputType::Auto, "input_type"}; - Setting input_device{"auto", "input_device"}; + Setting input_device{"Auto", "input_device"}; // Camera std::array camera_name; diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index c352dbdf0..ee81b177e 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -66,6 +66,10 @@ bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, uns } #endif + if (!layout.bottom_screen_enabled) { + return false; + } + Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue(); if (render_3d_mode == Settings::StereoRenderOption::SideBySide || diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 7dd42cecc..9b0d5a709 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -864,8 +864,10 @@ bool CIAFile::Close() { if (!complete) { LOG_ERROR(Service_AM, "CIAFile closed prematurely, aborting install..."); if (!is_additional_content) { - FileUtil::DeleteDirRecursively( - GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID())); + // Only delete the content folder as there may be user save data in the title folder. + const std::string title_content_path = + GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID()) + "content/"; + FileUtil::DeleteDirRecursively(title_content_path); } return true; } @@ -3253,9 +3255,15 @@ void Module::Interface::BeginImportProgramTemporarily(Kernel::HLERequestContext& // Create our CIAFile handle for the app to write to, and while the app writes Citra will store // contents out to sdmc/nand const FileSys::Path cia_path = {}; - auto file = std::make_shared( - am->system.Kernel(), std::make_unique(am->system, FS::MediaType::NAND), cia_path); + std::shared_ptr file; + { + auto cia_file = std::make_unique(am->system, FS::MediaType::NAND); + AuthorizeCIAFileDecryption(cia_file.get(), ctx); + + file = + std::make_shared(am->system.Kernel(), std::move(cia_file), cia_path); + } am->cia_installing = true; IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); @@ -3446,7 +3454,7 @@ void Module::Interface::GetProgramInfoFromCia(Kernel::HLERequestContext& ctx) { title_info.version = tmd.GetTitleVersion(); title_info.type = tmd.GetTitleType(); - IPC::RequestBuilder rb = rp.MakeBuilder(8, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(7, 0); rb.Push(ResultSuccess); rb.PushRaw(title_info); } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 4d04af017..070612250 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -1264,6 +1264,10 @@ std::string GetConsoleIdHash(Core::System& system) { return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), "")); } +std::array GetConsoleMacAddress(Core::System& system) { + return MacToArray(GetModule(system)->GetMacAddress()); +} + std::array MacToArray(const std::string& mac) { std::array ret; int last = -1; diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 7536680c0..8b02d6f8e 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -672,6 +672,8 @@ u64 MacToU64(const std::string& mac); std::string GenerateRandomMAC(); +std::array GetConsoleMacAddress(Core::System& system); + } // namespace Service::CFG SERVICE_CONSTRUCT(Service::CFG::Module) diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 3a08ffbee..1672feee3 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -106,15 +106,19 @@ void NWM_UDS::BroadcastNodeMap() { packet.channel = network_channel; packet.type = Network::WifiPacket::PacketType::NodeMap; packet.destination_address = Network::BroadcastMac; - std::size_t num_entries = std::count_if(node_map.begin(), node_map.end(), - [](const auto& node) { return node.second.connected; }); + auto node_can_broad = [](auto& node) -> bool { + return node.second.connected && !node.second.spec; + }; + std::size_t num_entries = + std::count_if(node_map.begin(), node_map.end(), + [&node_can_broad](const auto& node) { return node_can_broad(node); }); using node_t = decltype(node_map)::value_type; packet.data.resize(sizeof(num_entries) + (sizeof(node_t::first) + sizeof(node_t::second.node_id)) * num_entries); std::memcpy(packet.data.data(), &num_entries, sizeof(num_entries)); std::size_t offset = sizeof(num_entries); for (const auto& node : node_map) { - if (node.second.connected) { + if (node_can_broad(node)) { std::memcpy(packet.data.data() + offset, node.first.data(), sizeof(node.first)); std::memcpy(packet.data.data() + offset + sizeof(node.first), &node.second.node_id, sizeof(node.second.node_id)); @@ -185,7 +189,8 @@ void NWM_UDS::HandleAssociationResponseFrame(const Network::WifiPacket& packet) using Network::WifiPacket; WifiPacket eapol_start; eapol_start.channel = network_channel; - eapol_start.data = GenerateEAPoLStartFrame(std::get(assoc_result), current_node); + eapol_start.data = + GenerateEAPoLStartFrame(std::get(assoc_result), conn_type, current_node); // TODO(B3N30): Encrypt the packet. eapol_start.destination_address = packet.transmitter_address; eapol_start.type = WifiPacket::PacketType::Data; @@ -217,24 +222,36 @@ void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) { ASSERT(connection_status.max_nodes != connection_status.total_nodes); - auto node = DeserializeNodeInfoFromFrame(packet.data); + auto eapol_start = DeserializeEAPolStartPacket(packet.data); - // Get an unused network node id - u16 node_id = GetNextAvailableNodeId(); - node.network_node_id = node_id; + auto node = DeserializeNodeInfo(eapol_start.node); - connection_status.node_bitmask |= 1 << (node_id - 1); - connection_status.changed_nodes |= 1 << (node_id - 1); - connection_status.nodes[node_id - 1] = node.network_node_id; - connection_status.total_nodes++; + if (eapol_start.conn_type == ConnectionType::Client) { + // Get an unused network node id + u16 node_id = GetNextAvailableNodeId(); + node.network_node_id = node_id; - node_info[node_id - 1] = node; - network_info.total_nodes++; + connection_status.node_bitmask |= 1 << (node_id - 1); + connection_status.changed_nodes |= 1 << (node_id - 1); + connection_status.nodes[node_id - 1] = node.network_node_id; + connection_status.total_nodes++; - node_map[packet.transmitter_address].node_id = node.network_node_id; - node_map[packet.transmitter_address].connected = true; + node_info[node_id - 1] = node; + network_info.total_nodes++; - BroadcastNodeMap(); + node_map[packet.transmitter_address].node_id = node.network_node_id; + node_map[packet.transmitter_address].connected = true; + node_map[packet.transmitter_address].spec = false; + + BroadcastNodeMap(); + } else if (eapol_start.conn_type == ConnectionType::Spectator) { + node_map[packet.transmitter_address].node_id = NodeIDSpec; + node_map[packet.transmitter_address].connected = true; + node_map[packet.transmitter_address].spec = true; + } else { + LOG_ERROR(Service_NWM, "Client tried connecting with unknown connection type: 0x{:x}", + static_cast(eapol_start.conn_type)); + } // Send the EAPoL-Logoff packet. using Network::WifiPacket; @@ -282,15 +299,23 @@ void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) { node_info[index - 1] = DeserializeNodeInfo(node); } + if (conn_type == ConnectionType::Client) { + connection_status.status = NetworkStatus::ConnectedAsClient; + } else if (conn_type == ConnectionType::Spectator) { + connection_status.status = NetworkStatus::ConnectedAsSpectator; + } else { + LOG_ERROR(Service_NWM, "Unknown connection type: 0x{:x}", static_cast(conn_type)); + } + // We're now connected, signal the application - connection_status.status = NetworkStatus::ConnectedAsClient; connection_status.status_change_reason = NetworkStatusChangeReason::ConnectionEstablished; // Some games require ConnectToNetwork to block, for now it doesn't // If blocking is implemented this lock needs to be changed, // otherwise it might cause deadlocks connection_status_event->Signal(); connection_event->Signal(); - } else if (connection_status.status == NetworkStatus::ConnectedAsClient) { + } else if (connection_status.status == NetworkStatus::ConnectedAsClient || + connection_status.status == NetworkStatus::ConnectedAsSpectator) { // TODO(B3N30): Remove that section and send/receive a proper connection_status packet // On a 3ds this packet wouldn't be addressed to already connected clients // We use this information because in the current implementation the host @@ -328,9 +353,9 @@ void NWM_UDS::HandleSecureDataPacket(const Network::WifiPacket& packet) { std::scoped_lock lock{connection_status_mutex, system.Kernel().GetHLELock()}; if (connection_status.status != NetworkStatus::ConnectedAsHost && - connection_status.status != NetworkStatus::ConnectedAsClient) { - // TODO(B3N30): Handle spectators - LOG_DEBUG(Service_NWM, "Ignored SecureDataPacket, because connection status is {}", + connection_status.status != NetworkStatus::ConnectedAsClient && + connection_status.status != NetworkStatus::ConnectedAsSpectator) { + LOG_DEBUG(Service_NWM, "Ignored SecureDataPacket because connection status is {}", static_cast(connection_status.status)); return; } @@ -370,12 +395,14 @@ void NWM_UDS::HandleSecureDataPacket(const Network::WifiPacket& packet) { // TODO(B3N30): Allow more than one bind node per channel. auto channel_info = channel_data.find(secure_data.data_channel); // Ignore packets from channels we're not interested in. - if (channel_info == channel_data.end()) + if (channel_info == channel_data.end()) { return; + } if (channel_info->second.network_node_id != BroadcastNetworkNodeId && - channel_info->second.network_node_id != secure_data.src_node_id) + channel_info->second.network_node_id != secure_data.src_node_id) { return; + } // Add the received packet to the data queue. channel_info->second.received_packets.emplace_back(packet.data); @@ -432,7 +459,9 @@ void NWM_UDS::HandleAuthenticationFrame(const Network::WifiPacket& packet) { // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) { using Network::WifiPacket; - WifiPacket auth_request; + AuthenticationFrame auth_request; + memcpy(&auth_request, packet.data.data(), sizeof(auth_request)); + WifiPacket auth_response; { std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost) { @@ -454,13 +483,13 @@ void NWM_UDS::HandleAuthenticationFrame(const Network::WifiPacket& packet) { return; } // Respond with an authentication response frame with SEQ2 - auth_request.channel = network_channel; - auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); - auth_request.destination_address = packet.transmitter_address; - auth_request.type = WifiPacket::PacketType::Authentication; + auth_response.channel = network_channel; + auth_response.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); + auth_response.destination_address = packet.transmitter_address; + auth_response.type = WifiPacket::PacketType::Authentication; node_map[packet.transmitter_address].connected = false; } - SendPacket(auth_request); + SendPacket(auth_response); SendAssociationResponseFrame(packet.transmitter_address); } @@ -495,16 +524,16 @@ void NWM_UDS::HandleDeauthenticationFrame(const Network::WifiPacket& packet) { return; } - connection_status.node_bitmask &= ~(1 << (node.node_id - 1)); - connection_status.changed_nodes |= 1 << (node.node_id - 1); - connection_status.total_nodes--; - connection_status.nodes[node.node_id - 1] = 0; - - network_info.total_nodes--; - // TODO(B3N30): broadcast new connection_status to clients + if (!node.spec) { + connection_status.node_bitmask &= ~(1 << (node.node_id - 1)); + connection_status.changed_nodes |= 1 << (node.node_id - 1); + connection_status.total_nodes--; + connection_status.nodes[node.node_id - 1] = 0; + network_info.total_nodes--; + // TODO(B3N30): broadcast new connection_status to clients + } node_it->Reset(); - connection_status_event->Signal(); } @@ -588,14 +617,19 @@ void NWM_UDS::RecvBeaconBroadcastData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 out_buffer_size = rp.Pop(); + + // scan input struct u32 unk1 = rp.Pop(); u32 unk2 = rp.Pop(); MacAddress mac_address; rp.PopRaw(mac_address); + // uninitialized data in scan input struct rp.Skip(9, false); + // end scan input struct + u32 wlan_comm_id = rp.Pop(); u32 id = rp.Pop(); // From 3dbrew: @@ -1042,6 +1076,7 @@ void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) { } void NWM_UDS::DisconnectNetwork(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NWM, "disconnecting from network"); IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); @@ -1277,6 +1312,7 @@ void NWM_UDS::ConnectToNetwork(Kernel::HLERequestContext& ctx, u16 command_id, std::vector passphrase) { network_info = {}; std::memcpy(&network_info, network_info_buffer.data(), network_info_buffer.size()); + conn_type = static_cast(connection_type); // Start the connection sequence StartConnectionSequence(network_info.host_mac_address); diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index cf59e7bcd..045d4ba96 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -18,6 +18,7 @@ #include #include "common/common_types.h" #include "common/swap.h" +#include "core/hle/service/nwm/uds_common.h" #include "core/hle/service/service.h" #include "network/network.h" @@ -47,6 +48,8 @@ const u16 DefaultBeaconInterval = 100; /// The maximum number of nodes that can exist in an UDS session. constexpr u32 UDSMaxNodes = 16; +constexpr u16 NodeIDSpec = 0; + struct NodeInfo { u64_le friend_code_seed; std::array username; @@ -95,7 +98,7 @@ static_assert(sizeof(ConnectionStatus) == 0x30, "ConnectionStatus has incorrect struct NetworkInfo { std::array host_mac_address; u8 channel; - INSERT_PADDING_BYTES(1); + u8 unk1; u8 initialized; INSERT_PADDING_BYTES(3); std::array oui_value; @@ -477,7 +480,7 @@ private: void HandleSecureDataPacket(const Network::WifiPacket& packet); /* - * Start a connection sequence with an UDS server. The sequence starts by sending an 802.11 + * Start a connection sequence with a UDS server. The sequence starts by sending an 802.11 * authentication frame with SEQ1. */ void StartConnectionSequence(const MacAddress& server); @@ -526,6 +529,9 @@ private: // Node information about our own system. NodeInfo current_node; + // whether you are connecting as a client or a spec + ConnectionType conn_type; + struct BindNodeData { u32 bind_node_id; ///< Id of the bind node associated with this data. u8 channel; ///< Channel that this bind node was bound to. @@ -548,6 +554,7 @@ private: // Mapping of mac addresses to their respective node_ids. struct Node { bool connected; + bool spec; u16 node_id; private: diff --git a/src/core/hle/service/nwm/uds_common.h b/src/core/hle/service/nwm/uds_common.h new file mode 100644 index 000000000..fb2e118fa --- /dev/null +++ b/src/core/hle/service/nwm/uds_common.h @@ -0,0 +1,16 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::NWM { + +enum class ConnectionType : u8 { + Client = 0x1, + Spectator = 0x2, +}; + +}; // namespace Service::NWM diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp index abf2b36f9..305aec843 100644 --- a/src/core/hle/service/nwm/uds_data.cpp +++ b/src/core/hle/service/nwm/uds_data.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -286,9 +286,11 @@ SecureDataHeader ParseSecureDataHeader(std::span data) { return header; } -std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) { +std::vector GenerateEAPoLStartFrame(u16 association_id, ConnectionType conn_type, + const NodeInfo& node_info) { EAPoLStartPacket eapol_start{}; eapol_start.association_id = association_id; + eapol_start.conn_type = conn_type; eapol_start.node.friend_code_seed = node_info.friend_code_seed; std::copy(node_info.username.begin(), node_info.username.end(), @@ -327,13 +329,7 @@ NodeInfo DeserializeNodeInfoFromFrame(std::span frame) { // Skip the LLC header std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start)); - NodeInfo node{}; - node.friend_code_seed = eapol_start.node.friend_code_seed; - - std::copy(eapol_start.node.username.begin(), eapol_start.node.username.end(), - node.username.begin()); - - return node; + return DeserializeNodeInfo(eapol_start.node); } NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node) { @@ -380,4 +376,11 @@ EAPoLLogoffPacket ParseEAPoLLogoffFrame(std::span frame) { return eapol_logoff; } +EAPoLStartPacket DeserializeEAPolStartPacket(std::span frame) { + EAPoLStartPacket eapol_start; + + std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start)); + return eapol_start; +} + } // namespace Service::NWM diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h index 291cca0f5..faaf53448 100644 --- a/src/core/hle/service/nwm/uds_data.h +++ b/src/core/hle/service/nwm/uds_data.h @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -10,6 +10,7 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/nwm/uds_beacon.h" +#include "core/hle/service/nwm/uds_common.h" #include "core/hle/service/service.h" namespace Service::NWM { @@ -90,9 +91,8 @@ constexpr u16 EAPoLStartMagic = 0x201; struct EAPoLStartPacket { u16_be magic = EAPoLStartMagic; u16_be association_id; - // This value is hardcoded to 1 in the NWM module. - u16_be unknown = 1; - INSERT_PADDING_BYTES(2); + enum_le conn_type; + INSERT_PADDING_BYTES(3); EAPoLNodeInfo node; }; @@ -132,7 +132,8 @@ SecureDataHeader ParseSecureDataHeader(std::span data); * communication. * @returns The generated frame body. */ -std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info); +std::vector GenerateEAPoLStartFrame(u16 association_id, ConnectionType conn_type, + const NodeInfo& node_info); /* * Returns the EtherType of the specified 802.11 frame. @@ -151,6 +152,8 @@ u16 GetEAPoLFrameType(std::span frame); */ NodeInfo DeserializeNodeInfoFromFrame(std::span frame); +EAPoLStartPacket DeserializeEAPolStartPacket(std::span frame); + /* * Returns a NodeInfo constructed from the data in the specified EAPoLNodeInfo. */ diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index b574b574a..148d60185 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -78,7 +78,7 @@ const std::array service_module_map{ false}, {"CECD", 0x00040130'00002602, CECD::InstallInterfaces, false}, {"CFG", 0x00040130'00001702, CFG::InstallInterfaces, false}, - {"DLP", 0x00040130'00002802, DLP::InstallInterfaces, false}, + {"DLP", 0x00040130'00002802, DLP::InstallInterfaces, true}, {"DSP", 0x00040130'00001A02, DSP::InstallInterfaces, false}, {"FRD", 0x00040130'00003202, FRD::InstallInterfaces, true}, {"GSP", 0x00040130'00001C02, GSP::InstallInterfaces, false}, diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 61aa18928..514de372c 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -710,10 +710,7 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f } void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) { - // TODO: Allow for second layer opacity in portrait mode android - - if (!isPortrait && - (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) && + if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout && Settings::values.custom_second_layer_opacity.GetValue() < 100) { state.blend.src_rgb_func = GL_CONSTANT_ALPHA; state.blend.src_a_func = GL_CONSTANT_ALPHA; @@ -724,8 +721,7 @@ void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) { } void RendererOpenGL::ResetSecondLayerOpacity(bool isPortrait) { - if (!isPortrait && - (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) && + if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout && Settings::values.custom_second_layer_opacity.GetValue() < 100) { state.blend.src_rgb_func = GL_ONE; state.blend.dst_rgb_func = GL_ZERO; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index ee8b1910a..d4a63dd9b 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -376,7 +376,13 @@ void RendererVulkan::BuildPipelines() { }; const vk::PipelineColorBlendAttachmentState colorblend_attachment = { - .blendEnable = false, + .blendEnable = true, + .srcColorBlendFactor = vk::BlendFactor::eConstantAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusConstantAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eConstantAlpha, + .dstAlphaBlendFactor = vk::BlendFactor::eOneMinusConstantAlpha, + .alphaBlendOp = vk::BlendOp::eAdd, .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, }; @@ -385,7 +391,6 @@ void RendererVulkan::BuildPipelines() { .logicOpEnable = false, .attachmentCount = 1, .pAttachments = &colorblend_attachment, - .blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f}, }; const vk::Viewport placeholder_viewport = vk::Viewport{0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; @@ -398,6 +403,7 @@ void RendererVulkan::BuildPipelines() { }; const std::array dynamic_states = { + vk::DynamicState::eBlendConstants, vk::DynamicState::eViewport, vk::DynamicState::eScissor, }; @@ -729,6 +735,13 @@ void RendererVulkan::DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, fl }); } +void RendererVulkan::ApplySecondLayerOpacity(float alpha) { + scheduler.Record([alpha](vk::CommandBuffer cmdbuf) { + const std::array blend_constants = {0.0f, 0.0f, 0.0f, alpha}; + cmdbuf.setBlendConstants(blend_constants.data()); + }); +} + void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout, const Common::Rectangle& top_screen) { if (!layout.top_screen_enabled) { @@ -867,13 +880,30 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout& draw_info.modelview = MakeOrthographicMatrix(layout.width, layout.height); draw_info.layer = 0; + + // Apply the initial default opacity value; Needed to avoid flickering + ApplySecondLayerOpacity(1.0f); + + bool use_custom_opacity = + Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout && + Settings::values.custom_second_layer_opacity.GetValue() < 100; + float second_alpha = use_custom_opacity + ? Settings::values.custom_second_layer_opacity.GetValue() / 100.0f + : 1.0f; + if (!Settings::values.swap_screen.GetValue()) { DrawTopScreen(layout, top_screen); draw_info.layer = 0; + if (use_custom_opacity) { + ApplySecondLayerOpacity(second_alpha); + } DrawBottomScreen(layout, bottom_screen); } else { DrawBottomScreen(layout, bottom_screen); draw_info.layer = 0; + if (use_custom_opacity) { + ApplySecondLayerOpacity(second_alpha); + } DrawTopScreen(layout, top_screen); } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 8486915e4..15807a4f3 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -98,12 +98,16 @@ private: void DrawScreens(Frame* frame, const Layout::FramebufferLayout& layout, bool flipped); void DrawBottomScreen(const Layout::FramebufferLayout& layout, const Common::Rectangle& bottom_screen); + void DrawTopScreen(const Layout::FramebufferLayout& layout, const Common::Rectangle& top_screen); void DrawSingleScreen(u32 screen_id, float x, float y, float w, float h, Layout::DisplayOrientation orientation); void DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, float x, float y, float w, float h, Layout::DisplayOrientation orientation); + + void ApplySecondLayerOpacity(float alpha); + void LoadFBToScreenInfo(const Pica::FramebufferConfig& framebuffer, ScreenInfo& screen_info, bool right_eye); void FillScreen(Common::Vec3 color, const TextureInfo& texture); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 1ff478984..3a730ab5f 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -556,12 +556,15 @@ bool PipelineCache::EnsureDirectories() const { }; return create_dir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) && - create_dir(GetPipelineCacheDir()); + create_dir(GetVulkanDir()) && create_dir(GetPipelineCacheDir()); +} + +std::string PipelineCache::GetVulkanDir() const { + return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP; } std::string PipelineCache::GetPipelineCacheDir() const { - return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP + "pipeline" + - DIR_SEP; + return GetVulkanDir() + "pipeline" + DIR_SEP; } void PipelineCache::SwitchPipelineCache(u64 title_id, const std::atomic_bool& stop_loading, diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 650df5680..c4f2ea062 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -108,6 +108,9 @@ private: /// Create pipeline cache directories. Returns true on success. bool EnsureDirectories() const; + /// Returns the Vulkan shader directory + std::string GetVulkanDir() const; + /// Returns the pipeline cache storage dir std::string GetPipelineCacheDir() const; diff --git a/tools/README.md b/tools/README.md index c654936af..b4db3f866 100644 --- a/tools/README.md +++ b/tools/README.md @@ -7,7 +7,7 @@ The scripts in this directory assume that your current working directory is the ## Pre-release checklist - [ ] Update compatibility list -- [ ] Update translations +- [ ] If this is a major release (2123.1 -> major.minor), update translations ### Note: diff --git a/tools/purge-github-cache.sh b/tools/purge-github-cache.sh new file mode 100755 index 000000000..01fdb2fed --- /dev/null +++ b/tools/purge-github-cache.sh @@ -0,0 +1,6 @@ +#!/bin/bash -ex + +# This script assumes that the Github CLI is installed and that +# the authenticated user has appropriate authorization. + +gh cache delete --all \ No newline at end of file