From 2e3d926dd5e60a2d43f1f245a527a25b48002a3b Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Fri, 15 Aug 2025 17:47:35 +0100 Subject: [PATCH 01/24] Updated language translations via Transifex --- dist/languages/ru_RU.ts | 15 ++++++++------- .../app/src/main/res/values-b+pl+PL/strings.xml | 2 ++ 2 files changed, 10 insertions(+), 7 deletions(-) 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/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? From 3ab6a304cd84d03c03c424eee51016e3f40473b9 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Wed, 20 Aug 2025 14:48:34 +0200 Subject: [PATCH 02/24] am: fix save data being deleted on CIA install failure (#1319) --- src/core/hle/service/am/am.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 7dd42cecc..1b8c97bae 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; } From 2292f3ab1ba384da90c9b44747c5cce0ca6309f0 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Wed, 20 Aug 2025 13:57:05 +0100 Subject: [PATCH 03/24] Updated translations via Transifex --- dist/languages/ca_ES_valencia.ts | 54 +++++++++---------- dist/languages/es_ES.ts | 4 +- .../res/values-b+ca+ES+valencia/strings.xml | 15 ++++++ .../src/main/res/values-b+es+ES/strings.xml | 11 ++-- 4 files changed, 51 insertions(+), 33 deletions(-) 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/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? From 164b9329c7c4d10cf2a89492af2c716fd695eac5 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Sun, 31 Aug 2025 13:45:55 +0100 Subject: [PATCH 04/24] cmake: Corrected widespread incorrect usage of the SYSTEM property --- externals/CMakeLists.txt | 95 ++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 66cbbd52e..e9c1d5646 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 @@ -55,7 +64,7 @@ target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain) if(USE_SYSTEM_CRYPTOPP) find_package(cryptopp REQUIRED) add_library(cryptopp INTERFACE) - target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp) + target_link_libraries(cryptopp SYSTEM INTERFACE cryptopp::cryptopp) else() if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE) # TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC. @@ -73,13 +82,14 @@ 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 if(USE_SYSTEM_FMT) add_library(fmt INTERFACE) find_package(fmt REQUIRED) - target_link_libraries(fmt INTERFACE fmt::fmt) + target_link_libraries(fmt SYSTEM INTERFACE fmt::fmt) else() option(FMT_INSTALL "" ON) add_subdirectory(fmt EXCLUDE_FROM_ALL) @@ -91,7 +101,7 @@ if ("x86_64" IN_LIST ARCHITECTURE) if(USE_SYSTEM_XBYAK) find_package(xbyak REQUIRED) add_library(xbyak INTERFACE) - target_link_libraries(xbyak INTERFACE xbyak::xbyak) + target_link_libraries(xbyak SYSTEM INTERFACE xbyak::xbyak) else() add_subdirectory(xbyak EXCLUDE_FROM_ALL) endif() @@ -107,7 +117,7 @@ if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE) if(USE_SYSTEM_DYNARMIC) find_package(dynarmic REQUIRED) add_library(dynarmic INTERFACE) - target_link_libraries(dynarmic INTERFACE dynarmic::dynarmic) + target_link_libraries(dynarmic SYSTEM INTERFACE dynarmic::dynarmic) # The dynarmic package's cmake files are helpfully completely silent # so we have to inform the user of its status ourselves if(TARGET dynarmic::dynarmic) @@ -130,14 +140,15 @@ endif() if(USE_SYSTEM_INIH) find_package(inih REQUIRED COMPONENTS inih inir) add_library(inih INTERFACE) - target_link_libraries(inih INTERFACE inih::inih inih::inir) + target_link_libraries(inih SYSTEM INTERFACE inih::inih inih::inir) else() add_subdirectory(inih) 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 @@ -208,7 +221,7 @@ if(USE_SYSTEM_ZSTD) if(TARGET zstd::libzstd_shared) message(STATUS "Found system Zstandard") endif() - target_link_libraries(zstd INTERFACE zstd::libzstd_shared) + target_link_libraries(zstd SYSTEM INTERFACE zstd::libzstd_shared) else() set(ZSTD_LEGACY_SUPPORT OFF) set(ZSTD_BUILD_PROGRAMS OFF) @@ -243,7 +256,7 @@ endif() if(USE_SYSTEM_ENET) find_package(libenet REQUIRED) add_library(enet INTERFACE) - target_link_libraries(enet INTERFACE libenet::libenet) + target_link_libraries(enet SYSTEM INTERFACE libenet::libenet) else() add_subdirectory(enet) target_include_directories(enet INTERFACE ./enet/include) @@ -254,7 +267,7 @@ if (ENABLE_CUBEB) if(USE_SYSTEM_CUBEB) find_package(cubeb REQUIRED) add_library(cubeb INTERFACE) - target_link_libraries(cubeb INTERFACE cubeb::cubeb) + target_link_libraries(cubeb SYSTEM INTERFACE cubeb::cubeb) if(TARGET cubeb::cubeb) message(STATUS "Found system cubeb") endif() @@ -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) + target_link_libraries(httplib SYSTEM 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}) @@ -351,10 +369,11 @@ if (ENABLE_WEB_SERVICE) if (USE_SYSTEM_CPP_JWT) find_package(cpp-jwt REQUIRED) add_library(cpp-jwt INTERFACE) - target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt) + target_link_libraries(cpp-jwt SYSTEM 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() @@ -363,7 +382,7 @@ endif() if(USE_SYSTEM_LODEPNG) add_library(lodepng INTERFACE) find_package(lodepng REQUIRED) - target_link_libraries(lodepng INTERFACE lodepng::lodepng) + target_link_libraries(lodepng SYSTEM INTERFACE lodepng::lodepng) else() add_subdirectory(lodepng) endif() @@ -380,7 +399,7 @@ if (ENABLE_OPENAL) if(USE_SYSTEM_OPENAL) add_library(OpenAL INTERFACE) find_package(OpenAL REQUIRED) - target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL) + target_link_libraries(OpenAL SYSTEM INTERFACE OpenAL::OpenAL) else() set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "") set(ALSOFT_EXAMPLES OFF CACHE BOOL "") @@ -449,11 +468,12 @@ if (ENABLE_VULKAN) find_package(VulkanMemoryAllocator REQUIRED) if(TARGET GPUOpen::VulkanMemoryAllocator) message(STATUS "Found VulkanMemoryAllocator") - target_link_libraries(vma INTERFACE GPUOpen::VulkanMemoryAllocator) + target_link_libraries(vma SYSTEM INTERFACE GPUOpen::VulkanMemoryAllocator) 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 @@ -462,10 +482,11 @@ if (ENABLE_VULKAN) find_package(Vulkan REQUIRED) if(TARGET Vulkan::Headers) message(STATUS "Found Vulkan headers") - target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers) + target_link_libraries(vulkan-headers SYSTEM 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 From ec7f00c9a42874931426be90755975341220a3eb Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Tue, 2 Sep 2025 13:38:12 +0100 Subject: [PATCH 05/24] cmake: Added check for minimum AppleClang version --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a736eaac..b7627c37c 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() From ee58988897611f5a3aba11fcc383c1fde73d55e3 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Sat, 30 Aug 2025 13:25:33 +0100 Subject: [PATCH 06/24] cmake: Bump downloaded Qt version to 6.9.2 Also bumps aqtinstall to 3.3.0 --- CMakeLists.txt | 2 +- CMakeModules/DownloadExternals.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b7627c37c..324ddf1d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,7 +308,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..4ebb052fe 100644 --- a/CMakeModules/DownloadExternals.cmake +++ b/CMakeModules/DownloadExternals.cmake @@ -105,7 +105,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}") From d94657a44d940ce949a42feb2c3d1ff63b271ae2 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Sat, 30 Aug 2025 14:19:32 +0100 Subject: [PATCH 07/24] cmake: On Windows, download MSVC 2022 Qt versions instead of MSVC 2019 --- CMakeModules/DownloadExternals.cmake | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake index 4ebb052fe..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() From 3ef5bc0bfe6b1a6092830f3dcd9042ed8305da15 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Wed, 3 Sep 2025 02:03:17 +0100 Subject: [PATCH 08/24] macos: Patch `QMetalLayer.setNeedsDisplayInRect` at runtime to avoid freezing on recent Qt --- src/citra_meta/CMakeLists.txt | 4 +++ src/citra_qt/CMakeLists.txt | 15 +++++++++-- src/citra_qt/citra_qt.cpp | 6 +++++ src/citra_qt/qt_swizzle.h | 9 +++++++ src/citra_qt/qt_swizzle.mm | 48 +++++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/citra_qt/qt_swizzle.h create mode 100644 src/citra_qt/qt_swizzle.mm 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..4796fc922 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) @@ -275,6 +282,10 @@ if (NOT WIN32) target_include_directories(citra_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) endif() +if (APPLE) + target_link_libraries(citra_qt PRIVATE Qt6::GuiPrivate) +endif() + if (UNIX AND NOT APPLE) target_link_libraries(citra_qt PRIVATE Qt6::DBus gamemode) endif() diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index bc9b85661..523c3a3be 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 @@ -4112,6 +4113,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/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 From 29a77b342bd8e30a82737f64a419946ffbb09f6f Mon Sep 17 00:00:00 2001 From: DavidRGriswold Date: Wed, 3 Sep 2025 13:16:32 +0100 Subject: [PATCH 09/24] android: Prevent crash when editing a slider option with an out of bounds value Co-authored-by: OpenSauce04 --- .../features/settings/model/view/SliderSetting.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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. From 57995cd89ce473cc50558a8f3dba8b502d95d62c Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Wed, 3 Sep 2025 22:15:14 +0100 Subject: [PATCH 10/24] android: Bump Vulkan Validation Layers to SDK 1.4.313.0 --- src/android/app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 84006116f..e4aff1c8b 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -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) } From c888c40b3e9d1bbaa6dfb46269e5e153cb806363 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Wed, 3 Sep 2025 22:49:07 +0100 Subject: [PATCH 11/24] macos: Set `UIDesignRequiresCompatibility` to true --- dist/apple/Info.plist.in | 3 +++ 1 file changed, 3 insertions(+) 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 From beba099fedc31faa0de86fd8c796d8650720a2ee Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 4 Sep 2025 18:35:10 -0300 Subject: [PATCH 12/24] Fix android termination bug (#1354) * move hook additions to onCreateView * Updated license header * Formatting nitpick * Added prefix to log messages --------- Co-authored-by: OpenSauce04 --- .../citra_emu/fragments/EmulationFragment.kt | 39 ++++++++++--------- .../citra_emu/utils/EmulationLifecycleUtil.kt | 14 +++++-- 2 files changed, 32 insertions(+), 21 deletions(-) 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..fa2a34a2d 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 @@ -85,7 +85,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private val preferences: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) - private lateinit var emulationState: EmulationState + private var emulationState: EmulationState? = null private var perfStatsUpdater: Runnable? = null private lateinit var emulationActivity: EmulationActivity @@ -106,6 +106,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram if (context is EmulationActivity) { emulationActivity = context NativeLibrary.setEmulationActivity(context) + EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) + if (emulationState != null) { + EmulationLifecycleUtil.addShutdownHook(hook = { emulationState!!.stop() }) + } } else { throw IllegalStateException("EmulationFragment must have EmulationActivity parent") } @@ -156,8 +160,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram emulationState = EmulationState(game.path) emulationActivity = requireActivity() as EmulationActivity screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings) - EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) - EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) + EmulationLifecycleUtil.addShutdownHook(hook = { emulationState!!.stop() }) } override fun onCreateView( @@ -256,8 +259,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram binding.inGameMenu.setNavigationItemSelectedListener { when (it.itemId) { R.id.menu_emulation_pause -> { - if (emulationState.isPaused) { - emulationState.unpause() + if (emulationState!!.isPaused) { + emulationState!!.unpause() it.title = resources.getString(R.string.pause_emulation) it.icon = ResourcesCompat.getDrawable( resources, @@ -265,7 +268,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram requireContext().theme ) } else { - emulationState.pause() + emulationState!!.pause() it.title = resources.getString(R.string.resume_emulation) it.icon = ResourcesCompat.getDrawable( resources, @@ -355,7 +358,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } R.id.menu_exit -> { - emulationState.pause() + emulationState!!.pause() MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.emulation_close_game) .setMessage(R.string.emulation_close_game_message) @@ -363,9 +366,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram EmulationLifecycleUtil.closeGame() } .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> - emulationState.unpause() + emulationState!!.unpause() } - .setOnCancelListener { emulationState.unpause() } + .setOnCancelListener { emulationState!!.unpause() } .show() true } @@ -459,10 +462,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } private fun togglePause() { - if (emulationState.isPaused) { - emulationState.unpause() + if (emulationState!!.isPaused) { + emulationState!!.unpause() } else { - emulationState.pause() + emulationState!!.pause() } } @@ -470,7 +473,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram super.onResume() Choreographer.getInstance().postFrameCallback(this) if (NativeLibrary.isRunning()) { - emulationState.pause() + emulationState!!.pause() // If the overlay is enabled, we need to update the position if changed val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int @@ -488,7 +491,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } if (DirectoryInitialization.areCitraDirectoriesReady()) { - emulationState.run(emulationActivity.isActivityRecreated) + emulationState!!.run(emulationActivity.isActivityRecreated) } else { setupCitraDirectoriesThenStartEmulation() } @@ -496,7 +499,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram override fun onPause() { if (NativeLibrary.isRunning()) { - emulationState.pause() + emulationState!!.pause() } Choreographer.getInstance().removeFrameCallback(this) super.onPause() @@ -512,7 +515,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram if (directoryInitializationState === DirectoryInitializationState.CITRA_DIRECTORIES_INITIALIZED ) { - emulationState.run(emulationActivity.isActivityRecreated) + emulationState!!.run(emulationActivity.isActivityRecreated) } else if (directoryInitializationState === DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED ) { @@ -1348,11 +1351,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height) - emulationState.newSurface(holder.surface) + emulationState!!.newSurface(holder.surface) } override fun surfaceDestroyed(holder: SurfaceHolder) { - emulationState.clearSurface() + emulationState!!.clearSurface() } override fun doFrame(frameTimeNanos: Long) { 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..b729bbc08 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,11 +18,19 @@ object EmulationLifecycleUtil { } fun addShutdownHook(hook: Runnable) { - shutdownHooks.add(hook) + if (shutdownHooks.contains(hook)) { + Log.warning("[EmulationLifecycleUtil] Tried to add shutdown hook 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 that already existed. Skipping.") + } else { + pauseResumeHooks.add(hook) + } } fun clear() { From 1e2dd5ea7835ffb6db09ebfc5816422accb00a0a Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Thu, 4 Sep 2025 23:45:44 +0100 Subject: [PATCH 13/24] SecondaryDisplay.kt: Remove redundant SurfaceTexture, preventing log spam --- .../java/org/citra/citra_emu/display/SecondaryDisplay.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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..709bb222e 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 @@ -23,15 +23,12 @@ class SecondaryDisplay(val context: Context) { 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 ) } From 7f2ac3587011e2df0c65eab63be69086954a5994 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Fri, 5 Sep 2025 20:22:24 +0100 Subject: [PATCH 14/24] Revert "Fix android termination bug (#1354)" This reverts commit 70f9379eefc84b7651e3aababcce33987e073ed0. --- .../citra_emu/fragments/EmulationFragment.kt | 39 +++++++++---------- .../citra_emu/utils/EmulationLifecycleUtil.kt | 14 ++----- 2 files changed, 21 insertions(+), 32 deletions(-) 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 fa2a34a2d..fbf3e5525 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 @@ -85,7 +85,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private val preferences: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) - private var emulationState: EmulationState? = null + private lateinit var emulationState: EmulationState private var perfStatsUpdater: Runnable? = null private lateinit var emulationActivity: EmulationActivity @@ -106,10 +106,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram if (context is EmulationActivity) { emulationActivity = context NativeLibrary.setEmulationActivity(context) - EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) - if (emulationState != null) { - EmulationLifecycleUtil.addShutdownHook(hook = { emulationState!!.stop() }) - } } else { throw IllegalStateException("EmulationFragment must have EmulationActivity parent") } @@ -160,7 +156,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram emulationState = EmulationState(game.path) emulationActivity = requireActivity() as EmulationActivity screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings) - EmulationLifecycleUtil.addShutdownHook(hook = { emulationState!!.stop() }) + EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) + EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) } override fun onCreateView( @@ -259,8 +256,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram binding.inGameMenu.setNavigationItemSelectedListener { when (it.itemId) { R.id.menu_emulation_pause -> { - if (emulationState!!.isPaused) { - emulationState!!.unpause() + if (emulationState.isPaused) { + emulationState.unpause() it.title = resources.getString(R.string.pause_emulation) it.icon = ResourcesCompat.getDrawable( resources, @@ -268,7 +265,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram requireContext().theme ) } else { - emulationState!!.pause() + emulationState.pause() it.title = resources.getString(R.string.resume_emulation) it.icon = ResourcesCompat.getDrawable( resources, @@ -358,7 +355,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } R.id.menu_exit -> { - emulationState!!.pause() + emulationState.pause() MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.emulation_close_game) .setMessage(R.string.emulation_close_game_message) @@ -366,9 +363,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram EmulationLifecycleUtil.closeGame() } .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> - emulationState!!.unpause() + emulationState.unpause() } - .setOnCancelListener { emulationState!!.unpause() } + .setOnCancelListener { emulationState.unpause() } .show() true } @@ -462,10 +459,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } private fun togglePause() { - if (emulationState!!.isPaused) { - emulationState!!.unpause() + if (emulationState.isPaused) { + emulationState.unpause() } else { - emulationState!!.pause() + emulationState.pause() } } @@ -473,7 +470,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram super.onResume() Choreographer.getInstance().postFrameCallback(this) if (NativeLibrary.isRunning()) { - emulationState!!.pause() + emulationState.pause() // If the overlay is enabled, we need to update the position if changed val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int @@ -491,7 +488,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } if (DirectoryInitialization.areCitraDirectoriesReady()) { - emulationState!!.run(emulationActivity.isActivityRecreated) + emulationState.run(emulationActivity.isActivityRecreated) } else { setupCitraDirectoriesThenStartEmulation() } @@ -499,7 +496,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram override fun onPause() { if (NativeLibrary.isRunning()) { - emulationState!!.pause() + emulationState.pause() } Choreographer.getInstance().removeFrameCallback(this) super.onPause() @@ -515,7 +512,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram if (directoryInitializationState === DirectoryInitializationState.CITRA_DIRECTORIES_INITIALIZED ) { - emulationState!!.run(emulationActivity.isActivityRecreated) + emulationState.run(emulationActivity.isActivityRecreated) } else if (directoryInitializationState === DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED ) { @@ -1351,11 +1348,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height) - emulationState!!.newSurface(holder.surface) + emulationState.newSurface(holder.surface) } override fun surfaceDestroyed(holder: SurfaceHolder) { - emulationState!!.clearSurface() + emulationState.clearSurface() } override fun doFrame(frameTimeNanos: Long) { 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 b729bbc08..8f3b5dc07 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 Citra Emulator Project / Azahar Emulator Project +// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -18,19 +18,11 @@ object EmulationLifecycleUtil { } fun addShutdownHook(hook: Runnable) { - if (shutdownHooks.contains(hook)) { - Log.warning("[EmulationLifecycleUtil] Tried to add shutdown hook that already existed. Skipping.") - } else { - shutdownHooks.add(hook) - } + shutdownHooks.add(hook) } fun addPauseResumeHook(hook: Runnable) { - if (pauseResumeHooks.contains(hook)) { - Log.warning("[EmulationLifecycleUtil] Tried to add pause resume hook that already existed. Skipping.") - } else { - pauseResumeHooks.add(hook) - } + pauseResumeHooks.add(hook) } fun clear() { From 8519e92eae3c7734d674aa819d560a429b11e6e9 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Fri, 5 Sep 2025 17:05:35 +0300 Subject: [PATCH 15/24] android: Re-fixed game termination bug (#1357) * EmulationActivity and EmulationFragment clear only their own hooks * EmulationLifecycleUtil: Rename `remove()` to `removeHook()` * EmulationLifecycleUtil: Removed unused function `clear()` * Corrected somewhat incorrect usage of the word "hook" * Define `onShutdown` and `onPause` hook functions in constructors * Formatting nitpicks * Updated license header * Re-added log messages for attempting to add duplicate hooks --------- Co-authored-by: OpenSauce04 --- .../citra_emu/activities/EmulationActivity.kt | 26 ++++++++++--------- .../citra_emu/fragments/EmulationFragment.kt | 13 ++++++++-- .../citra_emu/utils/EmulationLifecycleUtil.kt | 24 ++++++++++++----- 3 files changed, 43 insertions(+), 20 deletions(-) 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/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index fbf3e5525..d3625693c 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 @@ -101,6 +101,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private val emulationViewModel: EmulationViewModel by activityViewModels() private val settingsViewModel: SettingsViewModel by viewModels() + private val onPause = Runnable{ togglePause() } + private val onShutdown = Runnable{ emulationState.stop() } + override fun onAttach(context: Context) { super.onAttach(context) if (context is EmulationActivity) { @@ -156,8 +159,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram emulationState = EmulationState(game.path) emulationActivity = requireActivity() as EmulationActivity screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings) - EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) - EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) + EmulationLifecycleUtil.addPauseResumeHook(onPause) + EmulationLifecycleUtil.addShutdownHook(onShutdown) } override fun onCreateView( @@ -507,6 +510,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 === 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) + } } } From 6ac0733002c9dd754b9821ac99538cead12943a2 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Fri, 5 Sep 2025 21:56:02 +0100 Subject: [PATCH 16/24] tools: Updated guidance regarding translation updates --- tools/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From a65114eabf4e3a217664ae79b89b36e030738410 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Fri, 5 Sep 2025 22:13:02 +0100 Subject: [PATCH 17/24] Updated compatibility list --- dist/compatibility_list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From a607e3dd22f89ef39c22913a8c1b4d6c25ebf1e1 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Sat, 13 Sep 2025 01:19:19 +0100 Subject: [PATCH 18/24] tools: Added Github cache purge script --- tools/purge-github-cache.sh | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100755 tools/purge-github-cache.sh 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 From 246e06d1a4e92a978152051b277091a8634b319b Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Sat, 13 Sep 2025 00:55:44 +0100 Subject: [PATCH 19/24] vk_pipeline_cache: Fix directory creation failure if `shaders/vulkan/` is missing --- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 9 ++++++--- src/video_core/renderer_vulkan/vk_pipeline_cache.h | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) 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; From 3e1b86548af5f94e9fcbd99fd5883168f26de5d8 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Tue, 16 Sep 2025 15:21:36 +0100 Subject: [PATCH 20/24] cmake: Remove `SYSTEM` from `target_link_libraries` --- externals/CMakeLists.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e9c1d5646..aa6e6c84b 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -64,7 +64,7 @@ target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain) if(USE_SYSTEM_CRYPTOPP) find_package(cryptopp REQUIRED) add_library(cryptopp INTERFACE) - target_link_libraries(cryptopp SYSTEM INTERFACE cryptopp::cryptopp) + target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp) else() if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE) # TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC. @@ -89,7 +89,7 @@ target_disable_warnings(dds-ktx) if(USE_SYSTEM_FMT) add_library(fmt INTERFACE) find_package(fmt REQUIRED) - target_link_libraries(fmt SYSTEM INTERFACE fmt::fmt) + target_link_libraries(fmt INTERFACE fmt::fmt) else() option(FMT_INSTALL "" ON) add_subdirectory(fmt EXCLUDE_FROM_ALL) @@ -101,7 +101,7 @@ if ("x86_64" IN_LIST ARCHITECTURE) if(USE_SYSTEM_XBYAK) find_package(xbyak REQUIRED) add_library(xbyak INTERFACE) - target_link_libraries(xbyak SYSTEM INTERFACE xbyak::xbyak) + target_link_libraries(xbyak INTERFACE xbyak::xbyak) else() add_subdirectory(xbyak EXCLUDE_FROM_ALL) endif() @@ -117,7 +117,7 @@ if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE) if(USE_SYSTEM_DYNARMIC) find_package(dynarmic REQUIRED) add_library(dynarmic INTERFACE) - target_link_libraries(dynarmic SYSTEM INTERFACE dynarmic::dynarmic) + target_link_libraries(dynarmic INTERFACE dynarmic::dynarmic) # The dynarmic package's cmake files are helpfully completely silent # so we have to inform the user of its status ourselves if(TARGET dynarmic::dynarmic) @@ -140,7 +140,7 @@ endif() if(USE_SYSTEM_INIH) find_package(inih REQUIRED COMPONENTS inih inir) add_library(inih INTERFACE) - target_link_libraries(inih SYSTEM INTERFACE inih::inih inih::inir) + target_link_libraries(inih INTERFACE inih::inih inih::inir) else() add_subdirectory(inih) endif() @@ -221,7 +221,7 @@ if(USE_SYSTEM_ZSTD) if(TARGET zstd::libzstd_shared) message(STATUS "Found system Zstandard") endif() - target_link_libraries(zstd SYSTEM INTERFACE zstd::libzstd_shared) + target_link_libraries(zstd INTERFACE zstd::libzstd_shared) else() set(ZSTD_LEGACY_SUPPORT OFF) set(ZSTD_BUILD_PROGRAMS OFF) @@ -256,7 +256,7 @@ endif() if(USE_SYSTEM_ENET) find_package(libenet REQUIRED) add_library(enet INTERFACE) - target_link_libraries(enet SYSTEM INTERFACE libenet::libenet) + target_link_libraries(enet INTERFACE libenet::libenet) else() add_subdirectory(enet) target_include_directories(enet INTERFACE ./enet/include) @@ -267,7 +267,7 @@ if (ENABLE_CUBEB) if(USE_SYSTEM_CUBEB) find_package(cubeb REQUIRED) add_library(cubeb INTERFACE) - target_link_libraries(cubeb SYSTEM INTERFACE cubeb::cubeb) + target_link_libraries(cubeb INTERFACE cubeb::cubeb) if(TARGET cubeb::cubeb) message(STATUS "Found system cubeb") endif() @@ -346,7 +346,7 @@ if(USE_SYSTEM_CPP_HTTPLIB) target_disable_warnings(httplib) else() if(CppHttp_FOUND) - target_link_libraries(httplib SYSTEM INTERFACE httplib::httplib) + 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 INTERFACE ./httplib) @@ -369,7 +369,7 @@ if (ENABLE_WEB_SERVICE) if (USE_SYSTEM_CPP_JWT) find_package(cpp-jwt REQUIRED) add_library(cpp-jwt INTERFACE) - target_link_libraries(cpp-jwt SYSTEM INTERFACE cpp-jwt::cpp-jwt) + target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt) else() add_library(cpp-jwt INTERFACE) target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include) @@ -382,7 +382,7 @@ endif() if(USE_SYSTEM_LODEPNG) add_library(lodepng INTERFACE) find_package(lodepng REQUIRED) - target_link_libraries(lodepng SYSTEM INTERFACE lodepng::lodepng) + target_link_libraries(lodepng INTERFACE lodepng::lodepng) else() add_subdirectory(lodepng) endif() @@ -399,7 +399,7 @@ if (ENABLE_OPENAL) if(USE_SYSTEM_OPENAL) add_library(OpenAL INTERFACE) find_package(OpenAL REQUIRED) - target_link_libraries(OpenAL SYSTEM INTERFACE OpenAL::OpenAL) + target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL) else() set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "") set(ALSOFT_EXAMPLES OFF CACHE BOOL "") @@ -468,7 +468,7 @@ if (ENABLE_VULKAN) find_package(VulkanMemoryAllocator REQUIRED) if(TARGET GPUOpen::VulkanMemoryAllocator) message(STATUS "Found VulkanMemoryAllocator") - target_link_libraries(vma SYSTEM INTERFACE GPUOpen::VulkanMemoryAllocator) + target_link_libraries(vma INTERFACE GPUOpen::VulkanMemoryAllocator) endif() else() add_library(vma INTERFACE) @@ -482,7 +482,7 @@ if (ENABLE_VULKAN) find_package(Vulkan REQUIRED) if(TARGET Vulkan::Headers) message(STATUS "Found Vulkan headers") - target_link_libraries(vulkan-headers SYSTEM INTERFACE Vulkan::Headers) + target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers) endif() else() target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include) From 724576cc61c7be925e3e3bdc8e94cd30f92f9289 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Fri, 3 Oct 2025 12:43:57 +0100 Subject: [PATCH 21/24] ci: Update all macOS runners to macOS 15 Sequoia --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81b406770..e38a07337 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-15' }} 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-15 needs: macos env: OS: macos From 897447e9bdd8ca3821de7046b75339f30ee39eb3 Mon Sep 17 00:00:00 2001 From: huesos_96 Date: Fri, 3 Oct 2025 07:45:49 -0600 Subject: [PATCH 22/24] Android: Dual screen fixes for Handhelds that have 2 screens like Ayaneo Pocket DS (#1341) * Prevent SecondaryDisplay from stealing focus The SecondaryDisplay Activity was stealing focus from the main Activity when it was launched. Set the `FLAG_NOT_FOCUSABLE` and `FLAG_NOT_TOUCH_MODAL` window flags to prevent the SecondaryDisplay from gaining focus. * Implement touch controls for secondary display This commit introduces touch input handling for the secondary display. The following changes were made: - Added `onSecondaryTouchEvent` and `onSecondaryTouchMoved` to `NativeLibrary.kt` and `native.cpp` to process touch events on the secondary display. - Implemented `onTouchListener` in `SecondaryDisplay.kt` to capture touch events and forward them to the native layer. - Handles `ACTION_DOWN`, `ACTION_POINTER_DOWN`, `ACTION_MOVE`, `ACTION_UP`, `ACTION_POINTER_UP`, and `ACTION_CANCEL` motion events. - Tracks the active pointer to ensure correct touch event handling. * Refactor display logic for multi-display support This commit introduces a `DisplayHelper` class to centralize display-related logic, particularly for handling scenarios where the application might be launched on an external display. Key changes: - Added `DisplayHelper.kt` to manage internal and external display identification based on launch conditions. - `MainActivity` and `EmulationActivity` now use `DisplayHelper.checkLaunchDisplay()` to determine the initial display. - `SecondaryDisplay` now uses `DisplayHelper.getExternalDisplay()` to correctly identify the target display for the secondary presentation. - `InputOverlay` now queries `DisplayHelper.isBottomOnPrimary()` to determine if touch input should be processed for the primary display based on the current screen layout. - `SecondaryDisplay` now queries `DisplayHelper.isBottomOnSecondary()` to conditionally pass touch events to the native layer based on which screen (primary or secondary) is currently displaying the 3DS bottom screen. These changes ensure that the application behaves correctly when launched on either the internal or an external display, and that touch input is routed appropriately based on the user's chosen screen layout for the dual screens. * Removed primary-screen checks so the input overlay always forwards touch events, ensuring all touches reach the native handler even when multiple displays are active * Remove DisplayHelper class and adjust external display logic * Formatting adjustments --------- Co-authored-by: DavidRGriswold Co-authored-by: OpenSauce04 --- .../java/org/citra/citra_emu/NativeLibrary.kt | 18 +++++ .../citra_emu/display/SecondaryDisplay.kt | 66 ++++++++++++++++--- .../citra/citra_emu/overlay/InputOverlay.kt | 3 +- src/android/app/src/main/jni/native.cpp | 19 ++++++ src/core/frontend/emu_window.cpp | 4 ++ 5 files changed, 98 insertions(+), 12 deletions(-) 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/display/SecondaryDisplay.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt index 709bb222e..e0594a7e5 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,11 +11,14 @@ 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) { private var pres: SecondaryDisplayPresentation? = null @@ -41,16 +44,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() @@ -58,13 +68,6 @@ 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 @@ -78,9 +81,16 @@ 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) @@ -100,6 +110,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/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/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/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 || From 4ac3cab0125e5a91c06b9f61643589f8de6a0139 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Fri, 3 Oct 2025 14:49:33 +0100 Subject: [PATCH 23/24] externals: Updated fmt to 12.0.0 This fixes a build failure with Clang 21 --- externals/fmt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 01dc2bb7769befd791006ee83d3cc014cb1db3bd Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 11 Sep 2025 22:24:33 +0300 Subject: [PATCH 24/24] android: Add Display Listener methods for smoother secondary display updates --- .../citra/citra_emu/display/SecondaryDisplay.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 e0594a7e5..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 @@ -20,7 +20,7 @@ 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 @@ -34,6 +34,7 @@ class SecondaryDisplay(val context: Context) { null, DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION ) + displayManager.registerDisplayListener(this, null) } fun updateSurface() { @@ -74,8 +75,20 @@ class SecondaryDisplay(val context: Context) { } 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