diff --git a/.ci/license-header.sh b/.ci/license-header.sh index d14d5adf42..fecffaa7d3 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -1,6 +1,7 @@ #!/bin/sh -e HEADER="$(cat "$PWD/.ci/license/header.txt")" +HEADER_HASH="$(cat "$PWD/.ci/license/header-hash.txt")" echo "Getting branch changes" @@ -13,41 +14,86 @@ FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r` echo "Done" +check_header() { + CONTENT="`head -n3 < $1`" + case "$CONTENT" in + "$HEADER"*) ;; + *) BAD_FILES="$BAD_FILES $1" ;; + esac +} + +check_cmake_header() { + CONTENT="`head -n3 < $1`" + + case "$CONTENT" in + "$HEADER_HASH"*) ;; + *) + BAD_CMAKE="$BAD_CMAKE $1" ;; + esac +} for file in $FILES; do [ -f "$file" ] || continue + if [ `basename -- "$file"` = "CMakeLists.txt" ]; then + check_cmake_header "$file" + continue + fi + EXTENSION="${file##*.}" case "$EXTENSION" in kts|kt|cpp|h) - CONTENT="`cat $file`" - case "$CONTENT" in - "$HEADER"*) ;; - *) BAD_FILES="$BAD_FILES $file" ;; - esac + check_header "$file" + ;; + cmake) + check_cmake_header "$file" ;; esac done -if [ "$BAD_FILES" = "" ]; then +if [ "$BAD_FILES" = "" ] && [ "$BAD_CMAKE" = "" ]; then echo echo "All good." exit fi -echo "The following files have incorrect license headers:" -echo +if [ "$BAD_FILES" != "" ]; then + echo "The following source files have incorrect license headers:" + echo -for file in $BAD_FILES; do echo $file; done + for file in $BAD_FILES; do echo $file; done -cat << EOF + cat << EOF -The following license header should be added to the start of all offending files: +The following license header should be added to the start of all offending SOURCE files: === BEGIN === $HEADER === END === +EOF + +fi + +if [ "$BAD_CMAKE" != "" ]; then + echo "The following CMake files have incorrect license headers:" + echo + + for file in $BAD_CMAKE; do echo $file; done + + cat << EOF + +The following license header should be added to the start of all offending CMake files: + +=== BEGIN === +$HEADER_HASH +=== END === + +EOF + +fi + +cat << EOF If some of the code in this PR is not being contributed by the original author, the files which have been exclusively changed by that code can be ignored. If this happens, this PR requirement can be bypassed once all other files are addressed. @@ -70,6 +116,17 @@ if [ "$FIX" = "true" ]; then git add $file done + for file in $BAD_CMAKE; do + cat $file > $file.bak + + cat .ci/license/header-hash.txt > $file + echo >> $file + cat $file.bak >> $file + + rm $file.bak + + git add $file + done echo "License headers fixed." if [ "$COMMIT" = "true" ]; then diff --git a/.ci/license/header-hash.txt b/.ci/license/header-hash.txt new file mode 100644 index 0000000000..91bc195e23 --- /dev/null +++ b/.ci/license/header-hash.txt @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/.ci/linux/build.sh b/.ci/linux/build.sh index 7c8bed1279..8e3a452809 100755 --- a/.ci/linux/build.sh +++ b/.ci/linux/build.sh @@ -104,6 +104,7 @@ cmake .. -G Ninja \ -DYUZU_USE_QT_WEB_ENGINE=$WEBENGINE \ -DYUZU_USE_FASTER_LD=ON \ -DYUZU_ENABLE_LTO=ON \ + -DDYNARMIC_ENABLE_LTO=ON \ "${EXTRA_CMAKE_FLAGS[@]}" ninja -j${NPROC} diff --git a/.ci/linux/eden.dwfsprof b/.ci/linux/eden.dwfsprof index bc360f0d46..9a3bee6f14 100644 --- a/.ci/linux/eden.dwfsprof +++ b/.ci/linux/eden.dwfsprof @@ -1,6 +1,6 @@ AppRun eden.desktop -org.eden_emu.eden.desktop +dev.eden_emu.eden.desktop shared/bin/eden shared/lib/lib.path shared/lib/ld-linux-x86-64.so.2 diff --git a/.ci/linux/package.sh b/.ci/linux/package.sh index 911fea2f7b..837cfe07ef 100755 --- a/.ci/linux/package.sh +++ b/.ci/linux/package.sh @@ -59,15 +59,15 @@ VERSION="$(echo "$EDEN_TAG")" mkdir -p ./AppDir cd ./AppDir -cp ../dist/org.eden_emu.eden.desktop . -cp ../dist/org.eden_emu.eden.svg . +cp ../dist/dev.eden_emu.eden.desktop . +cp ../dist/dev.eden_emu.eden.svg . -ln -sf ./org.eden_emu.eden.svg ./.DirIcon +ln -sf ./dev.eden_emu.eden.svg ./.DirIcon UPINFO='gh-releases-zsync|eden-emulator|Releases|latest|*.AppImage.zsync' if [ "$DEVEL" = 'true' ]; then - sed -i 's|Name=Eden|Name=Eden Nightly|' ./org.eden_emu.eden.desktop + sed -i 's|Name=Eden|Name=Eden Nightly|' ./dev.eden_emu.eden.desktop UPINFO="$(echo "$UPINFO" | sed 's|Releases|nightly|')" fi diff --git a/.ci/update-icons.sh b/.ci/update-icons.sh index 99adbfae66..4feb2abd24 100755 --- a/.ci/update-icons.sh +++ b/.ci/update-icons.sh @@ -6,7 +6,7 @@ which png2icns || [ which yay && yay libicns ] || exit which magick || exit -export EDEN_SVG_ICO="dist/org.eden_emu.eden.svg" +export EDEN_SVG_ICO="dist/dev.eden_emu.eden.svg" svgo --multipass $EDEN_SVG_ICO magick -density 256x256 -background transparent $EDEN_SVG_ICO \ diff --git a/.ci/windows/build.sh b/.ci/windows/build.sh index 7504630a57..681f327793 100644 --- a/.ci/windows/build.sh +++ b/.ci/windows/build.sh @@ -52,6 +52,7 @@ cmake .. -G Ninja \ -DYUZU_USE_QT_MULTIMEDIA=$MULTIMEDIA \ -DYUZU_USE_QT_WEB_ENGINE=$WEBENGINE \ -DYUZU_ENABLE_LTO=ON \ + -DDYNARMIC_ENABLE_LTO=ON \ "${EXTRA_CMAKE_FLAGS[@]}" ninja diff --git a/.patch/unordered-dense/0001-cmake.patch b/.patch/unordered-dense/0001-cmake.patch new file mode 100644 index 0000000000..39e7794b1f --- /dev/null +++ b/.patch/unordered-dense/0001-cmake.patch @@ -0,0 +1,22 @@ +From e59d30b7b12e1d04cc2fc9c6219e35bda447c17e Mon Sep 17 00:00:00 2001 +From: Lizzie <159065448+Lizzie841@users.noreply.github.com> +Date: Fri, 16 May 2025 04:12:13 +0100 +Subject: [PATCH] Update CMakeLists.txt + +--- + CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index b5f4c4f..c5c6f31 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -24,7 +24,7 @@ target_include_directories( + + target_compile_features(unordered_dense INTERFACE cxx_std_17) + +-if(_unordered_dense_is_toplevel_project) ++if(_unordered_dense_is_toplevel_project OR UNORDERED_DENSE_INSTALL) + # locations are provided by GNUInstallDirs + install( + TARGETS unordered_dense diff --git a/CMakeLists.txt b/CMakeLists.txt index d11b58bf1f..00dd5237be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,10 +48,10 @@ endif() # On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion CMAKE_DEPENDENT_OPTION(ENABLE_SDL2 "Enable the SDL2 frontend" ON "NOT ANDROID" OFF) -set(EXT_DEFAULT ON) +set(EXT_DEFAULT OFF) -if (PLATFORM_FREEBSD) - set(EXT_DEFAULT OFF) +if (MSVC OR ANDROID) + set(EXT_DEFAULT ON) endif() CMAKE_DEPENDENT_OPTION(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" ${EXT_DEFAULT} "ENABLE_SDL2;NOT MSVC" OFF) @@ -67,14 +67,13 @@ option(ENABLE_QT_UPDATE_CHECKER "Enable update checker for the Qt frontend" OFF) CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF) -option(YUZU_USE_CPM "Use CPM to fetch Eden dependencies if needed" ON) +option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT}) option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) option(ENABLE_WIFI_SCAN "Enable WiFi scanning" OFF) option(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ${EXT_DEFAULT}) -option(YUZU_USE_EXTERNAL_VULKAN_HEADERS "Use Vulkan-Headers from externals" ${EXT_DEFAULT}) -option(YUZU_USE_EXTERNAL_VULKAN_UTILITY_LIBRARIES "Use Vulkan-Utility-Libraries from externals" ${EXT_DEFAULT}) +option(YUZU_USE_EXTERNAL_VULKAN_UTILITY_LIBRARIES "Use Vulkan Utility Headers from externals" ${EXT_DEFAULT}) option(YUZU_USE_EXTERNAL_VULKAN_SPIRV_TOOLS "Use SPIRV-Tools from externals" ${EXT_DEFAULT}) option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF) @@ -93,10 +92,12 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}") option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ${EXT_DEFAULT}) +# TODO(crueter): CI this? option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON) option(FORCE_DOWNLOAD_WIN_BUNDLES "Forcefully download bundled Windows dependencies (useful for CI)" OFF) +# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system if (YUZU_USE_CPM AND ENABLE_SDL2) option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}") endif() @@ -105,18 +106,14 @@ CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Enable dedicated room functionality" ON "NOT A CMAKE_DEPENDENT_OPTION(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF) -CMAKE_DEPENDENT_OPTION(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID" OFF) +CMAKE_DEPENDENT_OPTION(YUZU_CMD "Compile the eden-cli executable" ON "ENABLE_SDL2;NOT ANDROID" OFF) CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF) -option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ${EXT_DEFAULT}) - option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON) -option(YUZU_ENABLE_PORTABLE "Allow yuzu to enable portable mode if a user folder is found in the CWD" ON) - CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF) @@ -194,53 +191,6 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE endif() endif() -# Sanity check : Check that all submodules are present -# ======================================================================= - -function(check_submodules_present) - file(READ "${PROJECT_SOURCE_DIR}/.gitmodules" gitmodules) - string(REGEX MATCHALL "path *= *[^ \t\r\n]*" gitmodules ${gitmodules}) - foreach(module ${gitmodules}) - string(REGEX REPLACE "path *= *" "" module ${module}) - - file(GLOB RESULT "${PROJECT_SOURCE_DIR}/${module}/*") - list(LENGTH RESULT RES_LEN) - if(RES_LEN EQUAL 0) - message(FATAL_ERROR "Git submodule ${module} not found. " - "Please run: \ngit submodule update --init --recursive") - endif() - if (EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git") - set(SUBMODULE_DIR "${PROJECT_SOURCE_DIR}/${module}") - - execute_process( - COMMAND git rev-parse --short=10 HEAD - WORKING_DIRECTORY ${SUBMODULE_DIR} - OUTPUT_VARIABLE SUBMODULE_SHA - ) - - # would probably be better to do string parsing, but whatever - execute_process( - COMMAND git remote get-url origin - WORKING_DIRECTORY ${SUBMODULE_DIR} - OUTPUT_VARIABLE SUBMODULE_URL - ) - - string(REGEX REPLACE "\n|\r" "" SUBMODULE_SHA ${SUBMODULE_SHA}) - string(REGEX REPLACE "\n|\r|\\.git" "" SUBMODULE_URL ${SUBMODULE_URL}) - - get_filename_component(SUBMODULE_NAME ${SUBMODULE_DIR} NAME) - - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_NAMES ${SUBMODULE_NAME}) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS ${SUBMODULE_SHA}) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_URLS ${SUBMODULE_URL}) - endif() - endforeach() -endfunction() - -if(EXISTS ${PROJECT_SOURCE_DIR}/.gitmodules AND YUZU_CHECK_SUBMODULES) - check_submodules_present() -endif() - configure_file(${PROJECT_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc COPYONLY) @@ -277,7 +227,7 @@ function(detect_architecture symbol arch) if (ARCHITECTURE_${arch}) set(ARCHITECTURE "${arch}" PARENT_SCOPE) set(ARCHITECTURE_${arch} 1 PARENT_SCOPE) - add_definitions(-DARCHITECTURE_${arch}=1) + add_compile_definitions(ARCHITECTURE_${arch}=1) endif() endif() endfunction() @@ -299,7 +249,7 @@ endif() if (NOT DEFINED ARCHITECTURE) set(ARCHITECTURE "GENERIC") set(ARCHITECTURE_GENERIC 1) - add_definitions(-DARCHITECTURE_GENERIC=1) + add_compile_definitions(ARCHITECTURE_GENERIC=1) endif() message(STATUS "Target architecture: ${ARCHITECTURE}") @@ -311,16 +261,16 @@ if (MSVC AND ARCHITECTURE_x86) endif() if (UNIX) - add_definitions(-DYUZU_UNIX=1) + add_compile_definitions(YUZU_UNIX=1) endif() if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX)) set(HAS_NCE 1) - add_definitions(-DHAS_NCE=1) + add_compile_definitions(HAS_NCE=1) endif() if (YUZU_ROOM) - add_definitions(-DYUZU_ROOM) + add_compile_definitions(YUZU_ROOM) endif() # Build/optimization presets @@ -406,8 +356,10 @@ if (YUZU_USE_CPM) if (NOT MSVC) # boost sucks - if (NOT PLATFORM_LINUX AND NOT ANDROID) - target_compile_definitions(boost_container INTERFACE BOOST_HAS_PTHREADS) + # Solaris (and probably other NIXes) need explicit pthread definition + if (PLATFORM_SUN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthreads") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthreads") endif() target_compile_options(boost_heap INTERFACE -Wno-shadow) @@ -599,7 +551,6 @@ endfunction() add_subdirectory(externals) # pass targets from externals -find_package(VulkanHeaders) find_package(VulkanUtilityLibraries) find_package(VulkanMemoryAllocator) find_package(SPIRV-Tools) @@ -707,7 +658,7 @@ if(ENABLE_QT) set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "") endif() -if (WIN32 AND YUZU_CRASH_DUMPS) +if (YUZU_CRASH_DUMPS) set(BREAKPAD_VER "breakpad-c89f9dd") download_bundled_external("breakpad/" ${BREAKPAD_VER} "breakpad-win" BREAKPAD_PREFIX "c89f9dd") @@ -734,7 +685,7 @@ if (APPLE) list(APPEND PLATFORM_LIBRARIES ${ICONV_LIBRARY}) elseif (WIN32) # Target Windows 10 - add_definitions(-D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00) + add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00) set(PLATFORM_LIBRARIES winmm ws2_32 iphlpapi) if (MINGW) # PSAPI is the Process Status API @@ -856,14 +807,14 @@ endif() # https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html # https://www.freedesktop.org/software/appstream/docs/ if(ENABLE_QT AND UNIX AND NOT APPLE) - install(FILES "dist/org.eden_emu.eden.desktop" + install(FILES "dist/dev.eden_emu.eden.desktop" DESTINATION "share/applications") - install(FILES "dist/org.eden_emu.eden.svg" + install(FILES "dist/dev.eden_emu.eden.svg" DESTINATION "share/icons/hicolor/scalable/apps") # TODO: these files need to be updated. - install(FILES "dist/org.eden_emu.eden.xml" + install(FILES "dist/dev.eden_emu.eden.xml" DESTINATION "share/mime/packages") - install(FILES "dist/org.eden_emu.eden.metainfo.xml" + install(FILES "dist/dev.eden_emu.eden.metainfo.xml" DESTINATION "share/metainfo") endif() diff --git a/CMakeModules/CPMUtil.cmake b/CMakeModules/CPMUtil.cmake index 4d7db6ed61..0fa37b9765 100644 --- a/CMakeModules/CPMUtil.cmake +++ b/CMakeModules/CPMUtil.cmake @@ -11,10 +11,11 @@ # Future crueter: Wow this was a lie and a half, at this point I might as well make my own CPN # haha just kidding... unless? +# TODO(crueter): Remember to get more than 6 hours of sleep whenever making giant cmake changes if (MSVC OR ANDROID) - set(BUNDLED_DEFAULT OFF) -else() set(BUNDLED_DEFAULT ON) +else() + set(BUNDLED_DEFAULT OFF) endif() option(CPMUTIL_FORCE_BUNDLED @@ -148,11 +149,32 @@ function(AddJsonPackage) get_json_element("${object}" tag tag "") get_json_element("${object}" artifact artifact "") get_json_element("${object}" git_version git_version "") + get_json_element("${object}" git_host git_host "") get_json_element("${object}" source_subdir source_subdir "") get_json_element("${object}" bundled bundled "unset") get_json_element("${object}" find_args find_args "") get_json_element("${object}" raw_patches patches "") + # okay here comes the fun part: REPLACEMENTS! + # first: tag gets %VERSION% replaced if applicable, with either git_version (preferred) or version + # second: artifact gets %VERSION% and %TAG% replaced accordingly (same rules for VERSION) + + if (git_version) + set(version_replace ${git_version}) + else() + set(version_replace ${version}) + endif() + + # TODO(crueter): fmt module for cmake + if (tag) + string(REPLACE "%VERSION%" "${version_replace}" tag ${tag}) + endif() + + if (artifact) + string(REPLACE "%VERSION%" "${version_replace}" artifact ${artifact}) + string(REPLACE "%TAG%" "${tag}" artifact ${artifact}) + endif() + # format patchdir if (raw_patches) math(EXPR range "${raw_patches_LENGTH} - 1") @@ -184,8 +206,6 @@ function(AddJsonPackage) # system/bundled if (bundled STREQUAL "unset" AND DEFINED JSON_BUNDLED_PACKAGE) set(bundled ${JSON_BUNDLED_PACKAGE}) - else() - set(bundled ON) endif() AddPackage( @@ -203,6 +223,8 @@ function(AddJsonPackage) SOURCE_SUBDIR "${source_subdir}" GIT_VERSION ${git_version} + GIT_HOST ${git_host} + ARTIFACT ${artifact} TAG ${tag} ) @@ -242,6 +264,7 @@ function(AddPackage) NAME VERSION GIT_VERSION + GIT_HOST REPO TAG @@ -259,6 +282,7 @@ function(AddPackage) KEY BUNDLED_PACKAGE + FIND_PACKAGE_ARGUMENTS ) set(multiValueArgs OPTIONS PATCHES) @@ -273,11 +297,17 @@ function(AddPackage) option(${PKG_ARGS_NAME}_FORCE_SYSTEM "Force the system package for ${PKG_ARGS_NAME}") option(${PKG_ARGS_NAME}_FORCE_BUNDLED "Force the bundled package for ${PKG_ARGS_NAME}") + if (NOT DEFINED PKG_ARGS_GIT_HOST) + set(git_host github.com) + else() + set(git_host ${PKG_ARGS_GIT_HOST}) + endif() + if (DEFINED PKG_ARGS_URL) set(pkg_url ${PKG_ARGS_URL}) if (DEFINED PKG_ARGS_REPO) - set(pkg_git_url https://github.com/${PKG_ARGS_REPO}) + set(pkg_git_url https://${git_host}/${PKG_ARGS_REPO}) else() if (DEFINED PKG_ARGS_GIT_URL) set(pkg_git_url ${PKG_ARGS_GIT_URL}) @@ -286,7 +316,7 @@ function(AddPackage) endif() endif() elseif (DEFINED PKG_ARGS_REPO) - set(pkg_git_url https://github.com/${PKG_ARGS_REPO}) + set(pkg_git_url https://${git_host}/${PKG_ARGS_REPO}) if (DEFINED PKG_ARGS_TAG) set(pkg_key ${PKG_ARGS_TAG}) @@ -317,25 +347,23 @@ function(AddPackage) cpm_utils_message(STATUS ${PKG_ARGS_NAME} "Download URL is ${pkg_url}") - if (DEFINED PKG_ARGS_GIT_VERSION) - set(git_version ${PKG_ARGS_GIT_VERSION}) - elseif(DEFINED PKG_ARGS_VERSION) - set(git_version ${PKG_ARGS_VERSION}) - endif() - if (NOT DEFINED PKG_ARGS_KEY) if (DEFINED PKG_ARGS_SHA) string(SUBSTRING ${PKG_ARGS_SHA} 0 4 pkg_key) cpm_utils_message(DEBUG ${PKG_ARGS_NAME} "No custom key defined, using ${pkg_key} from sha") - elseif (DEFINED git_version) - set(pkg_key ${git_version}) + elseif(DEFINED PKG_ARGS_GIT_VERSION) + set(pkg_key ${PKG_ARGS_GIT_VERSION}) cpm_utils_message(DEBUG ${PKG_ARGS_NAME} "No custom key defined, using ${pkg_key}") elseif (DEFINED PKG_ARGS_TAG) set(pkg_key ${PKG_ARGS_TAG}) cpm_utils_message(DEBUG ${PKG_ARGS_NAME} "No custom key defined, using ${pkg_key}") + elseif (DEFINED PKG_ARGS_VERSION) + set(pkg_key ${PKG_ARGS_VERSION}) + cpm_utils_message(DEBUG ${PKG_ARGS_NAME} + "No custom key defined, using ${pkg_key}") else() cpm_utils_message(WARNING ${PKG_ARGS_NAME} "Could not determine cache key, using CPM defaults") @@ -409,9 +437,9 @@ function(AddPackage) set_precedence(OFF OFF) elseif (CPMUTIL_FORCE_SYSTEM) set_precedence(ON ON) - elseif(NOT CPMUTIL_FORCE_BUNDLED) + elseif(CPMUTIL_FORCE_BUNDLED) set_precedence(OFF OFF) - elseif (DEFINED PKG_ARGS_BUNDLED_PACKAGE) + elseif (DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset") if (PKG_ARGS_BUNDLED_PACKAGE) set(local OFF) else() @@ -446,12 +474,15 @@ function(AddPackage) if (DEFINED PKG_ARGS_SHA) set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS ${PKG_ARGS_SHA}) - elseif(DEFINED git_version) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - ${git_version}) + elseif (DEFINED PKG_ARGS_GIT_VERSION) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS + ${PKG_ARGS_GIT_VERSION}) elseif (DEFINED PKG_ARGS_TAG) set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS ${PKG_ARGS_TAG}) + elseif(DEFINED PKG_ARGS_VERSION) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS + ${PKG_ARGS_VERSION}) else() cpm_utils_message(WARNING ${PKG_ARGS_NAME} "Package has no specified sha, tag, or version") @@ -496,6 +527,7 @@ function(add_ci_package key) set(ARTIFACT_DIR ${${ARTIFACT_PACKAGE}_SOURCE_DIR} PARENT_SCOPE) endfunction() +# TODO(crueter): we could do an AddMultiArchPackage, multiplatformpackage? # name is the artifact name, package is for find_package override function(AddCIPackage) set(oneValueArgs diff --git a/CMakeModules/Findmbedtls.cmake b/CMakeModules/Findmbedtls.cmake new file mode 100644 index 0000000000..f5ebf1abdc --- /dev/null +++ b/CMakeModules/Findmbedtls.cmake @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +include(FindPackageHandleStandardArgs) + +find_package(PkgConfig QUIET) +pkg_search_module(mbedtls QUIET IMPORTED_TARGET mbedtls) +find_package_handle_standard_args(mbedtls + REQUIRED_VARS mbedtls_LINK_LIBRARIES + VERSION_VAR mbedtls_VERSION +) + +pkg_search_module(mbedcrypto QUIET IMPORTED_TARGET mbedcrypto) +find_package_handle_standard_args(mbedcrypto + REQUIRED_VARS mbedcrypto_LINK_LIBRARIES + VERSION_VAR mbedcrypto_VERSION +) diff --git a/README.md b/README.md index 70f2c81296..e1f0b50b37 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ If you would like to contribute, we are open to new developers and pull requests * **Solaris**: [Solaris Building Guide](./docs/build/Solaris.md) * **FreeBSD**: [FreeBSD Building Guide](./docs/build/FreeBSD.md) * **macOS**: [macOS Building Guide](./docs/build/macOS.md) +* **OpenBSD**: [OpenBSD Building Guide](./docs/build/OpenBSD.md) ## Download diff --git a/cpmfile.json b/cpmfile.json index 495382fed0..c720b69e89 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -10,8 +10,8 @@ "boost": { "package": "Boost", "repo": "boostorg/boost", - "tag": "boost-1.88.0", - "artifact": "boost-1.88.0-cmake.7z", + "tag": "boost-%VERSION%", + "artifact": "%TAG%-cmake.7z", "hash": "e5b049e5b61964480ca816395f63f95621e66cb9bcf616a8b10e441e0e69f129e22443acb11e77bc1e8170f8e4171b9b7719891efc43699782bfcd4b3a365f01", "git_version": "1.88.0", "version": "1.57" diff --git a/dist/org.eden_emu.eden.desktop b/dist/dev.eden_emu.eden.desktop similarity index 95% rename from dist/org.eden_emu.eden.desktop rename to dist/dev.eden_emu.eden.desktop index d012ab6d07..5d2d7cd8c5 100644 --- a/dist/org.eden_emu.eden.desktop +++ b/dist/dev.eden_emu.eden.desktop @@ -10,7 +10,7 @@ Type=Application Name=Eden GenericName=Switch Emulator Comment=Nintendo Switch video game console emulator -Icon=org.eden_emu.eden +Icon=dev.eden_emu.eden TryExec=eden Exec=eden %f Categories=Game;Emulator;Qt; diff --git a/dist/org.eden_emu.eden.metainfo.xml b/dist/dev.eden_emu.eden.metainfo.xml similarity index 100% rename from dist/org.eden_emu.eden.metainfo.xml rename to dist/dev.eden_emu.eden.metainfo.xml diff --git a/dist/org.eden_emu.eden.svg b/dist/dev.eden_emu.eden.svg similarity index 100% rename from dist/org.eden_emu.eden.svg rename to dist/dev.eden_emu.eden.svg diff --git a/dist/org.eden_emu.eden.xml b/dist/dev.eden_emu.eden.xml similarity index 100% rename from dist/org.eden_emu.eden.xml rename to dist/dev.eden_emu.eden.xml diff --git a/docs/CPM.md b/docs/CPM.md index f90002891c..7f69cf669e 100644 --- a/docs/CPM.md +++ b/docs/CPM.md @@ -23,7 +23,7 @@ CPMUtil is a wrapper around CPM that aims to reduce boilerplate and add useful u - `NAME` (required): The package name (must be the same as the `find_package` name if applicable) - `VERSION`: The minimum version of this package that can be used on the system -- `GIT_VERSION`: The version found within git, only used for identification +- `GIT_VERSION`: The "version" found within git - `URL`: The URL to fetch. - `REPO`: The GitHub repo to use (`owner/repo`). * Only GitHub is supported for now, though other platforms will see support at some point @@ -71,8 +71,9 @@ Hashing strategies, descending order of precedence: - `KEY`: Custom cache key to use (stored as `.cache/cpm/${packagename_lower}/${key}`) * Default is based on, in descending order of precedence: - First 4 characters of the sha - - `GIT_VERSION`, or `VERSION` if not specified + - `GIT_VERSION` - Tag + - `VERSION` - Otherwise, CPM defaults will be used. This is not recommended as it doesn't produce reproducible caches - `DOWNLOAD_ONLY`: Whether or not to configure the downloaded package via CMake * Useful to turn `OFF` if the project doesn't use CMake diff --git a/docs/Development.md b/docs/Development.md index e60384e8ab..e4816cd1ec 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -6,6 +6,7 @@ * **Solaris**: [Solaris Building Guide](./build/Solaris.md) * **FreeBSD**: [FreeBSD Building Guide](./build/FreeBSD.md) * **macOS**: [macOS Building Guide](./build/macOS.md) +* **OpenBSD**: [OpenBSD Building Guide](./build/OpenBSD.md) # CPM diff --git a/docs/User.md b/docs/User.md new file mode 100644 index 0000000000..cfc81063f8 --- /dev/null +++ b/docs/User.md @@ -0,0 +1,11 @@ +# User configuration + +## Configuration directories + +Eden will store configuration in the following directories: + +- **Windows**: `%AppData%\Roaming`. +- **Android**: Data is stored internally. +- **Linux, macOS, FreeBSD, Solaris, OpenBSD**: `$XDG_DATA_HOME`, `$XDG_CACHE_HOME`, `$XDG_CONFIG_HOME`. + +If a `user` directory is present in the current working directory, that will override all global configuration directories and the emulator will use that instead. diff --git a/docs/build/Android.md b/docs/build/Android.md index 4bb1c868b6..0538d351ea 100644 --- a/docs/build/Android.md +++ b/docs/build/Android.md @@ -1,42 +1,42 @@ -# Note: These build instructions are a work-in-progress. - -## Dependencies -* [Android Studio](https://developer.android.com/studio) -* [NDK 25.2.9519653 and CMake 3.22.1](https://developer.android.com/studio/projects/install-ndk#default-version) -* [Git](https://git-scm.com/download) - -### WINDOWS ONLY - Additional Dependencies - * **[Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)** - **Make sure to select "Desktop development with C++" support in the installer. Make sure to update to the latest version if already installed.** - * **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** - **Make sure to select Latest SDK.** - - A convenience script to install the latest SDK is provided in `.ci\windows\install-vulkan-sdk.ps1`. - -## Cloning Eden with Git -``` -git clone --recursive https://git.eden-emu.dev/eden-emu/eden.git -``` -Eden by default will be cloned into - -* `C:\Users\\eden` on Windows -* `~/eden` on Linux -* And wherever on macOS - -## Building -1. Start Android Studio, on the startup dialog select `Open`. -2. Navigate to the `eden/src/android` directory and click on `OK`. -3. In `Build > Select Build Variant`, select `release` or `relWithDebInfo` as the "Active build variant". -4. Build the project with `Build > Make Project` or run it on an Android device with `Run > Run 'app'`. - -## Building with Terminal -1. Download the SDK and NDK from Android Studio. -2. Navigate to SDK and NDK paths. -3. Then set ANDROID_SDK_ROOT and ANDROID_NDK_ROOT in terminal via -`export ANDROID_SDK_ROOT=path/to/sdk` -`export ANDROID_NDK_ROOT=path/to/ndk`. -4. Navigate to `eden/src/android`. -5. Then Build with `./gradlew assemblerelWithDebInfo`. -6. To build the optimised build use `./gradlew assembleGenshinSpoofRelWithDebInfo`. - -### Script -A convenience script for building is provided in `.ci/android/build.sh`. The built APK can be put into an `artifacts` directory via `.ci/android/package.sh`. On Windows, these must be done in the Git Bash or MinGW terminal. - -### Additional Resources -https://developer.android.com/studio/intro +# Note: These build instructions are a work-in-progress. + +## Dependencies +* [Android Studio](https://developer.android.com/studio) +* [NDK 25.2.9519653 and CMake 3.22.1](https://developer.android.com/studio/projects/install-ndk#default-version) +* [Git](https://git-scm.com/download) + +### WINDOWS ONLY - Additional Dependencies + * **[Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)** - **Make sure to select "Desktop development with C++" support in the installer. Make sure to update to the latest version if already installed.** + * **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** - **Make sure to select Latest SDK.** + - A convenience script to install the latest SDK is provided in `.ci\windows\install-vulkan-sdk.ps1`. + +## Cloning Eden with Git +``` +git clone --recursive https://git.eden-emu.dev/eden-emu/eden.git +``` +Eden by default will be cloned into - +* `C:\Users\\eden` on Windows +* `~/eden` on Linux +* And wherever on macOS + +## Building +1. Start Android Studio, on the startup dialog select `Open`. +2. Navigate to the `eden/src/android` directory and click on `OK`. +3. In `Build > Select Build Variant`, select `release` or `relWithDebInfo` as the "Active build variant". +4. Build the project with `Build > Make Project` or run it on an Android device with `Run > Run 'app'`. + +## Building with Terminal +1. Download the SDK and NDK from Android Studio. +2. Navigate to SDK and NDK paths. +3. Then set ANDROID_SDK_ROOT and ANDROID_NDK_ROOT in terminal via +`export ANDROID_SDK_ROOT=path/to/sdk` +`export ANDROID_NDK_ROOT=path/to/ndk`. +4. Navigate to `eden/src/android`. +5. Then Build with `./gradlew assemblerelWithDebInfo`. +6. To build the optimised build use `./gradlew assembleGenshinSpoofRelWithDebInfo`. + +### Script +A convenience script for building is provided in `.ci/android/build.sh`. The built APK can be put into an `artifacts` directory via `.ci/android/package.sh`. On Windows, these must be done in the Git Bash or MinGW terminal. + +### Additional Resources +https://developer.android.com/studio/intro diff --git a/docs/build/FreeBSD.md b/docs/build/FreeBSD.md index 475378125c..97eef8f9d8 100644 --- a/docs/build/FreeBSD.md +++ b/docs/build/FreeBSD.md @@ -1,85 +1,81 @@ -## One word of caution before proceeding. - -This is not the usual or preferred way to build programs on FreeBSD. -As of writing there is no official fresh port available for Eden, but it is in the works. -After it is available you can find a link to the eden-emu fresh port here and on Escary's github repo. -See this build as an AppImage alternative for FreeBSD. - -## Dependencies. -Before we start we need some dependencies. -These dependencies are generally needed to build Eden on FreeBSD. - -``` -devel/cmake -devel/sdl20 -devel/boost-libs -devel/catch2 -devel/libfmt -devel/nlohmann-json -devel/ninja -devel/nasm -devel/autoconf -devel/pkgconf -devel/qt6-base - -multimedia/ffnvcodec-headers -multimedia/ffmpeg - -audio/opus - -archivers/liblz4 - -lang/gcc12 - -graphics/glslang -graphics/vulkan-utility-libraries -``` - -If using FreeBSD 12 or prior, use `devel/pkg-config` instead. - ---- - -### Build preparations: -Run the following command to clone eden with git: -```sh -git clone --recursive https://git.eden-emu.dev/eden-emu/eden -``` -You usually want to add the `--recursive` parameter as it also takes care of the external dependencies for you. - -Now change into the eden directory and create a build directory there: -```sh -cd eden -mkdir build -``` - -Change into that build directory: -```sh -cd build -``` - -#### 1. Building in Release Mode (usually preferred and the most performant choice): -```sh -cmake .. -GNinja -DYUZU_TESTS=OFF -``` - -#### 2. Building in Release Mode with debugging symbols (useful if you want to debug errors for a eventual fix): -```sh -cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=ON -``` - -Build the emulator locally: -```sh -ninja -``` - -Optional: If you wish to install eden globally onto your system issue the following command: -```sh -sudo ninja install -``` -OR -```sh -doas -- ninja install -``` - -## OpenSSL +Eden is not currently available as a port on FreeBSD, though it is in the works. For now, the recommended method of usage is to compile it yourself. Check back often, as the build process frequently changes. + +## Dependencies. +Eden needs the following dependencies: + +``` +devel/cmake +devel/sdl20 +devel/boost-libs +devel/catch2 +devel/libfmt +devel/nlohmann-json +devel/ninja +devel/nasm +devel/autoconf +devel/pkgconf +devel/qt6-base + +net/enet + +multimedia/ffnvcodec-headers +multimedia/ffmpeg + +audio/opus + +archivers/liblz4 + +lang/gcc12 + +graphics/glslang +graphics/vulkan-utility-libraries +``` + +If using FreeBSD 12 or prior, use `devel/pkg-config` instead. + +--- + +### Build preparations: +Run the following command to clone eden with git: +```sh +git clone --recursive https://git.eden-emu.dev/eden-emu/eden +``` +You usually want to add the `--recursive` parameter as it also takes care of the external dependencies for you. + +Now change into the eden directory and create a build directory there: +```sh +cd eden +mkdir build +``` + +Change into that build directory: +```sh +cd build +``` + +#### 1. Building in Release Mode (usually preferred and the most performant choice): +```sh +cmake .. -GNinja -DYUZU_TESTS=OFF +``` + +#### 2. Building in Release Mode with debugging symbols (useful if you want to debug errors for a eventual fix): +```sh +cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=ON +``` + +Build the emulator locally: +```sh +ninja +``` + +Optional: If you wish to install eden globally onto your system issue the following command: +```sh +sudo ninja install +``` +OR +```sh +doas -- ninja install +``` + +## OpenSSL The available OpenSSL port (3.0.17) is out-of-date, and using a bundled static library instead is recommended; to do so, add `-DYUZU_USE_CPM=ON` to your CMake configure command. \ No newline at end of file diff --git a/docs/build/Linux.md b/docs/build/Linux.md index be58b451fa..a6b7b2dda7 100644 --- a/docs/build/Linux.md +++ b/docs/build/Linux.md @@ -1,138 +1,138 @@ -### Dependencies - -You'll need to download and install the following to build Eden: - - * [GCC](https://gcc.gnu.org/) v11+ (for C++20 support) & misc - * If GCC 12 is installed, [Clang](https://clang.llvm.org/) v14+ is required for compiling - * [CMake](https://www.cmake.org/) 3.22+ - -The following are handled by Eden's externals: - - * [FFmpeg](https://ffmpeg.org/) - * [SDL2](https://www.libsdl.org/download-2.0.php) 2.0.18+ - * [opus](https://opus-codec.org/downloads/) 1.3+ - -All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/) if `YUZU_USE_CPM` is on, but will always use system dependencies if available: - - * [Boost](https://www.boost.org/users/download/) 1.79.0+ - * [Catch2](https://github.com/catchorg/Catch2) 2.13.7 - 2.13.9 - * [fmt](https://fmt.dev/) 8.0.1+ - * [lz4](http://www.lz4.org) 1.8+ - * [nlohmann_json](https://github.com/nlohmann/json) 3.8+ - * [OpenSSL](https://www.openssl.org/source/) 1.1.1+ - * [ZLIB](https://www.zlib.net/) 1.2+ - * [zstd](https://facebook.github.io/zstd/) 1.5+ - * [enet](http://enet.bespin.org/) 1.3+ - * [cubeb](https://github.com/mozilla/cubeb) - * [SimpleIni](https://github.com/brofield/simpleini) - -Certain other dependencies (httplib, jwt, sirit, etc.) will be fetched by CPM regardless. System packages *can* be used for these libraries but this is generally not recommended. - -Dependencies are listed here as commands that can be copied/pasted. Of course, they should be inspected before being run. - -- Arch / Manjaro: - - `sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 mbedtls ninja nlohmann-json openssl opus qt6-base qt6-multimedia sdl2 zlib zstd zip unzip` - - Building with QT Web Engine requires `qt6-webengine` as well. - - Proper wayland support requires `qt6-wayland` - - GCC 11 or later is required. - -- Ubuntu / Linux Mint / Debian: - - `sudo apt-get install autoconf cmake g++ gcc git glslang-tools libasound2 libboost-context-dev libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev` - - Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required. - - Users need to manually specify building with QT Web Engine enabled. This is done using the parameter `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake. - - Users need to manually disable building SDL2 from externals if they intend to use the version provided by their system by adding the parameters `-DYUZU_USE_EXTERNAL_SDL2=OFF` - -```sh -git submodule update --init --recursive -cmake .. -GNinja -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -``` - -- Fedora: - - `sudo dnf install autoconf ccache cmake fmt-devel gcc{,-c++} glslang hidapi-devel json-devel libtool libusb1-devel libzstd-devel lz4-devel nasm ninja-build openssl-devel pulseaudio-libs-devel qt6-linguist qt6-qtbase{-private,}-devel qt6-qtwebengine-devel qt6-qtmultimedia-devel speexdsp-devel wayland-devel zlib-devel ffmpeg-devel libXext-devel` - - Fedora 32 or later is required. - - Due to GCC 12, Fedora 36 or later users need to install `clang`, and configure CMake to use it via `-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang` - - CMake arguments to force system libraries: - - SDL2: `-DYUZU_USE_BUNDLED_SDL2=OFF -DYUZU_USE_EXTERNAL_SDL2=OFF` - - FFmpeg: `-DYUZU_USE_EXTERNAL_FFMPEG=OFF` - - [RPM Fusion](https://rpmfusion.org/) (free) is required to install `ffmpeg-devel` - -### Cloning Eden with Git - -**Master:** - -```bash -git clone --recursive https://git.eden-emu.dev/eden-emu/eden -cd eden -``` - -The `--recursive` option automatically clones the required Git submodules. - -### Building Eden in Release Mode (Optimised) - -If you need to run ctests, you can disable `-DYUZU_TESTS=OFF` and install Catch2. - -```bash -mkdir build && cd build -cmake .. -GNinja -DYUZU_TESTS=OFF -ninja -sudo ninja install -``` -You may also want to include support for Discord Rich Presence by adding `-DUSE_DISCORD_PRESENCE=ON` after `cmake ..` - -`-DYUZU_USE_EXTERNAL_VULKAN_SPIRV_TOOLS=OFF` might be needed if ninja command failed with `undefined reference to symbol 'spvOptimizerOptionsCreate`, reason currently unknown - -Optionally, you can use `cmake-gui ..` to adjust various options (e.g. disable the Qt GUI). - -### Building Eden in Debug Mode (Slow) - -```bash -mkdir build && cd build -cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DYUZU_TESTS=OFF -ninja -``` - -### Building with debug symbols - -```bash -mkdir build && cd build -cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU -DYUZU_TESTS=OFF -ninja -``` - -### Building with Scripts -A convenience script for building is provided in `.ci/linux/build.sh`. You must provide an arch target for optimization, e.g. `.ci/linux/build.sh amd64`. Valid targets: -- `legacy`: x86_64 generic, only needed for CPUs older than 2013 or so -- `amd64`: x86_64-v3, for CPUs newer than 2013 or so -- `steamdeck` / `zen2`: For Steam Deck or Zen >= 2 AMD CPUs (untested on Intel) -- `rog-ally` / `allyx` / `zen4`: For ROG Ally X or Zen >= 4 AMD CPUs (untested on Intel) -- `aarch64`: For armv8-a CPUs, older than mid-2021 or so -- `armv9`: For armv9-a CPUs, newer than mid-2021 or so -- `native`: Optimize to your native host architecture - -Extra flags to pass to CMake should be passed after the arch target. - -Additional environment variables can be used to control building: -- `NPROC`: Number of threads to use for compilation (defaults to all) -- `TARGET`: Set to `appimage` to disable standalone `eden-cli` and `eden-room` executables -- `BUILD_TYPE`: Sets the build type to use. Defaults to `Release` - -The following environment variables are boolean flags. Set to `true` to enable or `false` to disable: -- `DEVEL` (default FALSE): Disable Qt update checker -- `USE_WEBENGINE` (default FALSE): Enable Qt WebEngine -- `USE_MULTIMEDIA` (default TRUE): Enable Qt Multimedia - -After building, an AppImage can be packaged via `.ci/linux/package.sh`. This script takes the same arch targets as the build script. If the build was created in a different directory, you can specify its path relative to the source directory, e.g. `.ci/linux/package.sh amd64 build-appimage`. Additionally, set the `DEVEL` environment variable to `true` to change the app name to `Eden Nightly`. - -### Running without installing - -After building, the binaries `eden` and `eden-cmd` (depending on your build options) will end up in `build/bin/`. - -```bash -# SDL -cd build/bin/ -./eden-cmd - -# Qt -cd build/bin/ -./eden -``` +### Dependencies + +You'll need to download and install the following to build Eden: + + * [GCC](https://gcc.gnu.org/) v11+ (for C++20 support) & misc + * If GCC 12 is installed, [Clang](https://clang.llvm.org/) v14+ is required for compiling + * [CMake](https://www.cmake.org/) 3.22+ + +The following are handled by Eden's externals: + + * [FFmpeg](https://ffmpeg.org/) + * [SDL2](https://www.libsdl.org/download-2.0.php) 2.0.18+ + * [opus](https://opus-codec.org/downloads/) 1.3+ + +All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/) if `YUZU_USE_CPM` is on, but will always use system dependencies if available: + + * [Boost](https://www.boost.org/users/download/) 1.79.0+ + * [Catch2](https://github.com/catchorg/Catch2) 2.13.7 - 2.13.9 + * [fmt](https://fmt.dev/) 8.0.1+ + * [lz4](http://www.lz4.org) 1.8+ + * [nlohmann_json](https://github.com/nlohmann/json) 3.8+ + * [OpenSSL](https://www.openssl.org/source/) 1.1.1+ + * [ZLIB](https://www.zlib.net/) 1.2+ + * [zstd](https://facebook.github.io/zstd/) 1.5+ + * [enet](http://enet.bespin.org/) 1.3+ + * [cubeb](https://github.com/mozilla/cubeb) + * [SimpleIni](https://github.com/brofield/simpleini) + +Certain other dependencies (httplib, jwt, sirit, etc.) will be fetched by CPM regardless. System packages *can* be used for these libraries but this is generally not recommended. + +Dependencies are listed here as commands that can be copied/pasted. Of course, they should be inspected before being run. + +- Arch / Manjaro: + - `sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 mbedtls ninja nlohmann-json openssl opus qt6-base qt6-multimedia sdl2 zlib zstd zip unzip` + - Building with QT Web Engine requires `qt6-webengine` as well. + - Proper wayland support requires `qt6-wayland` + - GCC 11 or later is required. + +- Ubuntu / Linux Mint / Debian: + - `sudo apt-get install autoconf cmake g++ gcc git glslang-tools libasound2 libboost-context-dev libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev` + - Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required. + - Users need to manually specify building with QT Web Engine enabled. This is done using the parameter `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake. + - Users need to manually disable building SDL2 from externals if they intend to use the version provided by their system by adding the parameters `-DYUZU_USE_EXTERNAL_SDL2=OFF` + +```sh +git submodule update --init --recursive +cmake .. -GNinja -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 +``` + +- Fedora: + - `sudo dnf install autoconf ccache cmake fmt-devel gcc{,-c++} glslang hidapi-devel json-devel libtool libusb1-devel libzstd-devel lz4-devel nasm ninja-build openssl-devel pulseaudio-libs-devel qt6-linguist qt6-qtbase{-private,}-devel qt6-qtwebengine-devel qt6-qtmultimedia-devel speexdsp-devel wayland-devel zlib-devel ffmpeg-devel libXext-devel` + - Fedora 32 or later is required. + - Due to GCC 12, Fedora 36 or later users need to install `clang`, and configure CMake to use it via `-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang` + - CMake arguments to force system libraries: + - SDL2: `-DYUZU_USE_BUNDLED_SDL2=OFF -DYUZU_USE_EXTERNAL_SDL2=OFF` + - FFmpeg: `-DYUZU_USE_EXTERNAL_FFMPEG=OFF` + - [RPM Fusion](https://rpmfusion.org/) (free) is required to install `ffmpeg-devel` + +### Cloning Eden with Git + +**Master:** + +```bash +git clone --recursive https://git.eden-emu.dev/eden-emu/eden +cd eden +``` + +The `--recursive` option automatically clones the required Git submodules. + +### Building Eden in Release Mode (Optimised) + +If you need to run ctests, you can disable `-DYUZU_TESTS=OFF` and install Catch2. + +```bash +mkdir build && cd build +cmake .. -GNinja -DYUZU_TESTS=OFF +ninja +sudo ninja install +``` +You may also want to include support for Discord Rich Presence by adding `-DUSE_DISCORD_PRESENCE=ON` after `cmake ..` + +`-DYUZU_USE_EXTERNAL_VULKAN_SPIRV_TOOLS=OFF` might be needed if ninja command failed with `undefined reference to symbol 'spvOptimizerOptionsCreate`, reason currently unknown + +Optionally, you can use `cmake-gui ..` to adjust various options (e.g. disable the Qt GUI). + +### Building Eden in Debug Mode (Slow) + +```bash +mkdir build && cd build +cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DYUZU_TESTS=OFF +ninja +``` + +### Building with debug symbols + +```bash +mkdir build && cd build +cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU -DYUZU_TESTS=OFF +ninja +``` + +### Building with Scripts +A convenience script for building is provided in `.ci/linux/build.sh`. You must provide an arch target for optimization, e.g. `.ci/linux/build.sh amd64`. Valid targets: +- `legacy`: x86_64 generic, only needed for CPUs older than 2013 or so +- `amd64`: x86_64-v3, for CPUs newer than 2013 or so +- `steamdeck` / `zen2`: For Steam Deck or Zen >= 2 AMD CPUs (untested on Intel) +- `rog-ally` / `allyx` / `zen4`: For ROG Ally X or Zen >= 4 AMD CPUs (untested on Intel) +- `aarch64`: For armv8-a CPUs, older than mid-2021 or so +- `armv9`: For armv9-a CPUs, newer than mid-2021 or so +- `native`: Optimize to your native host architecture + +Extra flags to pass to CMake should be passed after the arch target. + +Additional environment variables can be used to control building: +- `NPROC`: Number of threads to use for compilation (defaults to all) +- `TARGET`: Set to `appimage` to disable standalone `eden-cli` and `eden-room` executables +- `BUILD_TYPE`: Sets the build type to use. Defaults to `Release` + +The following environment variables are boolean flags. Set to `true` to enable or `false` to disable: +- `DEVEL` (default FALSE): Disable Qt update checker +- `USE_WEBENGINE` (default FALSE): Enable Qt WebEngine +- `USE_MULTIMEDIA` (default TRUE): Enable Qt Multimedia + +After building, an AppImage can be packaged via `.ci/linux/package.sh`. This script takes the same arch targets as the build script. If the build was created in a different directory, you can specify its path relative to the source directory, e.g. `.ci/linux/package.sh amd64 build-appimage`. Additionally, set the `DEVEL` environment variable to `true` to change the app name to `Eden Nightly`. + +### Running without installing + +After building, the binaries `eden` and `eden-cmd` (depending on your build options) will end up in `build/bin/`. + +```bash +# SDL +cd build/bin/ +./eden-cmd + +# Qt +cd build/bin/ +./eden +``` diff --git a/docs/build/OpenBSD.md b/docs/build/OpenBSD.md new file mode 100644 index 0000000000..6a55fd269d --- /dev/null +++ b/docs/build/OpenBSD.md @@ -0,0 +1,10 @@ +# Building for OpenBSD + +```sh +pkg_add -u +pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake llvm-19.1.7p3 qt6 jq +git --recursive https://git.eden-emu.dev/eden-emu/eden +cmake -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DDYNARMIC_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_BUILD_TYPE=Debug -DENABLE_QT=OFF -DENABLE_OPENSSL=OFF -DENABLE_WEB_SERVICE=OFF -B /usr/obj/eden +``` + +- Modify `externals/ffmpeg/CMakeFiles/ffmpeg-build/build.make` to use `-j$(nproc)` instead of just `-j`. diff --git a/docs/build/Solaris.md b/docs/build/Solaris.md index d4cfdbb6a9..f7174c2869 100644 --- a/docs/build/Solaris.md +++ b/docs/build/Solaris.md @@ -1,51 +1,51 @@ -# Building for Solaris - -## Dependencies. -Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability. - -Run the usual update + install of essential toolings: `sudo pkg update && sudo pkg install git cmake`. - -- **gcc**: `sudo pkg install developer/gcc-14`. -- **clang**: Version 20 is broken, use `sudo pkg install developer/clang-19`. - -Then install the libraies: `sudo pkg install qt6 boost glslang libzip library/lz4 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt`. - -### Building - -Clone eden with git `git clone --recursive https://git.eden-emu.dev/eden-emu/eden` - -```sh -# Needed for some dependencies that call cc directly (tz) -echo '#!/bin/sh' >cc -echo 'gcc $@' >>cc -chmod +x cc -export PATH="$PATH:$PWD" -``` - -Patch for FFmpeg: -```sh -sed -i 's/ make / gmake /' externals/ffmpeg/CMakeFiles/ffmpeg-build.dir/build.make -``` - -- **Configure**: `cmake -B build -DYUZU_USE_CPM=ON -DCMAKE_CXX_FLAGS="-I/usr/include/SDL2" -DCMAKE_C_FLAGS="-I/usr/include/SDL2"`. -- **Build**: `cmake --build build`. -- **Installing**: `sudo cmake --install build`. - -### Running - -Default Mesa is a bit outdated, the following environment variables should be set for a smoother experience: -```sh -export MESA_GL_VERSION_OVERRIDE=4.6 -export MESA_GLSL_VERSION_OVERRIDE=460 -export MESA_EXTENSION_MAX_YEAR=2025 -export MESA_DEBUG=1 -export MESA_VK_VERSION_OVERRIDE=1.3 -# Only if nvidia/intel drm drivers cause crashes, will severely hinder performance -export LIBGL_ALWAYS_SOFTWARE=1 -``` - -### Notes - -- Modify the generated ffmpeg.make (in build dir) if using multiple threads (base system `make` doesn't use `-j4`, so change for `gmake`). -- If using OpenIndiana, due to a bug in SDL2 cmake configuration; Audio driver defaults to SunOS ``, which does not exist on OpenIndiana. +# Building for Solaris + +## Dependencies. +Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability. + +Run the usual update + install of essential toolings: `sudo pkg update && sudo pkg install git cmake`. + +- **gcc**: `sudo pkg install developer/gcc-14`. +- **clang**: Version 20 is broken, use `sudo pkg install developer/clang-19`. + +Then install the libraies: `sudo pkg install qt6 boost glslang libzip library/lz4 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt`. + +### Building + +Clone eden with git `git clone --recursive https://git.eden-emu.dev/eden-emu/eden` + +```sh +# Needed for some dependencies that call cc directly (tz) +echo '#!/bin/sh' >cc +echo 'gcc $@' >>cc +chmod +x cc +export PATH="$PATH:$PWD" +``` + +Patch for FFmpeg: +```sh +sed -i 's/ make / gmake /' externals/ffmpeg/CMakeFiles/ffmpeg-build.dir/build.make +``` + +- **Configure**: `cmake -B build -DYUZU_USE_CPM=ON -DCMAKE_CXX_FLAGS="-I/usr/include/SDL2" -DCMAKE_C_FLAGS="-I/usr/include/SDL2"`. +- **Build**: `cmake --build build`. +- **Installing**: `sudo cmake --install build`. + +### Running + +Default Mesa is a bit outdated, the following environment variables should be set for a smoother experience: +```sh +export MESA_GL_VERSION_OVERRIDE=4.6 +export MESA_GLSL_VERSION_OVERRIDE=460 +export MESA_EXTENSION_MAX_YEAR=2025 +export MESA_DEBUG=1 +export MESA_VK_VERSION_OVERRIDE=1.3 +# Only if nvidia/intel drm drivers cause crashes, will severely hinder performance +export LIBGL_ALWAYS_SOFTWARE=1 +``` + +### Notes + +- Modify the generated ffmpeg.make (in build dir) if using multiple threads (base system `make` doesn't use `-j4`, so change for `gmake`). +- If using OpenIndiana, due to a bug in SDL2 cmake configuration; Audio driver defaults to SunOS ``, which does not exist on OpenIndiana. - System OpenSSL generally does not work. Instead, use `-DYUZU_USE_CPM=ON` to use a bundled static OpenSSL, or build a system dependency from source. \ No newline at end of file diff --git a/docs/build/Windows.md b/docs/build/Windows.md index 3b8c459073..c1792983aa 100644 --- a/docs/build/Windows.md +++ b/docs/build/Windows.md @@ -1,193 +1,193 @@ -# THIS GUIDE IS INTENDED FOR DEVELOPERS ONLY, SUPPORT WILL ONLY BE GIVEN IF YOU'RE A DEVELOPER. - -## Method I: MSVC Build for Windows - -### Minimal Dependencies - -On Windows, all library dependencies are automatically included within the `externals` folder, or can be downloaded on-demand. To build Eden, you need to install: - - * **[Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)** - **Make sure to select C++ support in the installer. Make sure to update to the latest version if already installed.** - * **[CMake](https://cmake.org/download/)** - Used to generate Visual Studio project files. Does not matter if either 32-bit or 64-bit version is installed. - * **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** - **Make sure to select Latest SDK.** - - A convenience script to install the latest SDK is provided in `.ci\windows\install-vulkan-sdk.ps1`. - - ![2](https://i.imgur.com/giDwuTm.png) - - * **Git** - We recommend [Git for Windows](https://gitforwindows.org). - - ![3](https://i.imgur.com/UeSzkBw.png) - - * While installing Git Bash, you should tell it to include Git in your system path. (Choose the "Git from the command line and also from 3rd-party software" option.) If you missed that, don't worry, you'll just have to manually tell CMake where your git.exe is, since it's used to include version info into the built executable. - - ![4](https://i.imgur.com/x0rRs1t.png) - -### Cloning Eden with Git - -**Master:** - ```cmd - git clone --recursive https://git.eden-emu.dev/eden-emu/eden - cd eden - ``` - - ![9](https://i.imgur.com/CcxIAht.png) - -* *(Note: eden by default downloads to `C:\Users\\eden` (Master) - -### Building - -* Open the CMake GUI application and point it to the `eden` (Master) - - ![10](https://i.imgur.com/qOslIWv.png) - -* For the build directory, use a `/build` subdirectory inside the source directory or some other directory of your choice. (Tell CMake to create it.) - -* Click the "Configure" button and choose `Visual Studio 17 2022`, with `x64` for the optional platform. - - ![12](https://i.imgur.com/DKiREaK.png) - - * *(Note: If you used GitHub's own app to clone, run `git submodule update --init --recursive` to get the remaining dependencies)* - - * *(You may also want to disable `YUZU_TESTS` in this case since Catch2 is not yet supported with this.)* - - ![13](https://user-images.githubusercontent.com/22451773/180585999-07316d6e-9751-4d11-b957-1cf57cd7cd58.png) - -* Click "Generate" to create the project files. - - ![15](https://i.imgur.com/5LKg92k.png) - -* Open the solution file `yuzu.sln` in Visual Studio 2022, which is located in the build folder. - - ![16](https://i.imgur.com/208yMml.png) - -* Depending if you want a graphical user interface or not (`eden` has the graphical user interface, while `eden-cmd` doesn't), select `eden` or `eden-cmd` in the Solution Explorer, right-click and `Set as StartUp Project`. - - ![17](https://i.imgur.com/nPMajnn.png) ![18](https://i.imgur.com/BDMLzRZ.png) - -* Select the appropriate build type, Debug for debug purposes or Release for performance (in case of doubt choose Release). - - ![19](https://i.imgur.com/qxg4roC.png) - -* Right-click the project you want to build and press Build in the submenu or press F5. - - ![20](https://i.imgur.com/CkQgOFW.png) - -## Method II: MinGW-w64 Build with MSYS2 - -### Prerequisites to install - -* [MSYS2](https://www.msys2.org) -* [Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows) - **Make sure to select Latest SDK.** -* Make sure to follow the instructions and update to the latest version by running `pacman -Syu` as many times as needed. - -### Install eden dependencies for MinGW-w64 - -* Open the `MSYS2 MinGW 64-bit` (mingw64.exe) shell -* Download and install all dependencies using: `pacman -Syu git make mingw-w64-x86_64-SDL2 mingw-w64-x86_64-cmake mingw-w64-x86_64-python-pip mingw-w64-x86_64-qt6 mingw-w64-x86_64-toolchain autoconf libtool automake-wrapper` -* Add MinGW binaries to the PATH: `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc` -* Add glslangValidator to the PATH: `echo 'PATH=$(readlink -e /c/VulkanSDK/*/Bin/):$PATH' >> ~/.bashrc` - -### Clone the eden repository with Git - - ```bash - git clone --recursive https://git.eden-emu.dev/eden-emu/eden - cd eden - ``` - -### Run the following commands to build eden (dynamically linked build) - -```bash -mkdir build && cd build -cmake -G "MSYS Makefiles" -DYUZU_TESTS=OFF .. -make -j$(nproc) -# test eden out with -./bin/eden.exe -``` - -* *(Note: This build is not a static build meaning that you need to include all of the DLLs with the .exe in order to use it!)* - -e.g. -```Bash -cp externals/ffmpeg-*/bin/*.dll bin/ -``` - -Bonus Note: Running programs from inside `MSYS2 MinGW x64` shell has a different %PATH% than directly from explorer. This different %PATH% has the locations of the other DLLs required. -![image](https://user-images.githubusercontent.com/190571/165000848-005e8428-8a82-41b1-bb4d-4ce7797cdac8.png) - - -### Building without Qt (Optional) - -Doesn't require the rather large Qt dependency, but you will lack a GUI frontend: - - * Pass the `-DENABLE_QT=no` flag to cmake - -## Method III: CLion Environment Setup - -### Minimal Dependencies - -To build eden, you need to install the following: - -* [CLion](https://www.jetbrains.com/clion/) - This IDE is not free; for a free alternative, check Method I -* [Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows) - Make sure to select the Latest SDK. - -### Cloning eden with CLion - -* Clone the Repository: - -![1](https://user-images.githubusercontent.com/42481638/216899046-0d41d7d6-8e4d-4ed2-9587-b57088af5214.png) -![2](https://user-images.githubusercontent.com/42481638/216899061-b2ea274a-e88c-40ae-bf0b-4450b46e9fea.png) -![3](https://user-images.githubusercontent.com/42481638/216899076-0e5988c4-d431-4284-a5ff-9ecff973db76.png) - - - -### Building & Setup - -* Once Cloned, You will be taken to a prompt like the image below: - -![4](https://user-images.githubusercontent.com/42481638/216899092-3fe4cec6-a540-44e3-9e1e-3de9c2fffc2f.png) - -* Set the settings to the image below: -* Change `Build type: Release` -* Change `Name: Release` -* Change `Toolchain Visual Studio` -* Change `Generator: Let CMake decide` -* Change `Build directory: build` - -![5](https://user-images.githubusercontent.com/42481638/216899164-6cee8482-3d59-428f-b1bc-e6dc793c9b20.png) - -* Click OK; now Clion will build a directory and index your code to allow for IntelliSense. Please be patient. -* Once this process has been completed (No loading bar bottom right), you can now build eden -* In the top right, click on the drop-down menu, select all configurations, then select eden - -![6](https://user-images.githubusercontent.com/42481638/216899226-975048e9-bc6d-4ec1-bc2d-bd8a1e15ed04.png) - -* Now run by clicking the play button or pressing Shift+F10, and eden will auto-launch once built. - -![7](https://user-images.githubusercontent.com/42481638/216899275-d514ec6a-e563-470e-81e2-3e04f0429b68.png) - -## Building from the command line with MSVC - -```cmd -git clone --recursive https://git.eden-emu.dev/eden-emu/eden -cd eden -mkdir build -cd build -cmake .. -G "Visual Studio 17 2022" -A x64 -cmake --build . --config Release -``` - -### Building with Scripts -A convenience script for building is provided in `.ci/windows/build.sh`. You must run this with Bash, e.g. Git Bash or MinGW TTY. To use this script, you must have windeployqt installed (usually bundled with Qt) and set the `WINDEPLOYQT` environment variable to its canonical Bash location, e.g. `WINDEPLOYQT="/c/Qt/6.9.1/msvc2022_64/bin/windeployqt6.exe" .ci/windows/build.sh`. - -Extra CMake flags should be placed in the arguments of the script. - -Additional environment variables can be used to control building: -- `BUILD_TYPE`: Sets the build type to use. Defaults to `Release` - -The following environment variables are boolean flags. Set to `true` to enable or `false` to disable: -- `DEVEL` (default FALSE): Disable Qt update checker -- `USE_WEBENGINE` (default FALSE): Enable Qt WebEngine -- `USE_MULTIMEDIA` (default TRUE): Enable Qt Multimedia -- `BUNDLE_QT` (default FALSE): Use bundled Qt - * Note that using system Qt requires you to include the Qt CMake directory in `CMAKE_PREFIX_PATH`, e.g. `.ci/windows/build.sh -DCMAKE_PREFIX_PATH=C:/Qt/6.9.0/msvc2022_64/lib/cmake/Qt6` - -After building, a zip can be packaged via `.ci/windows/package.sh`. Note that you must have 7-zip installed and in your PATH. The resulting zip will be placed into `artifacts` in the source directory. +# THIS GUIDE IS INTENDED FOR DEVELOPERS ONLY, SUPPORT WILL ONLY BE GIVEN IF YOU'RE A DEVELOPER. + +## Method I: MSVC Build for Windows + +### Minimal Dependencies + +On Windows, all library dependencies are automatically included within the `externals` folder, or can be downloaded on-demand. To build Eden, you need to install: + + * **[Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)** - **Make sure to select C++ support in the installer. Make sure to update to the latest version if already installed.** + * **[CMake](https://cmake.org/download/)** - Used to generate Visual Studio project files. Does not matter if either 32-bit or 64-bit version is installed. + * **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** - **Make sure to select Latest SDK.** + - A convenience script to install the latest SDK is provided in `.ci\windows\install-vulkan-sdk.ps1`. + + ![2](https://i.imgur.com/giDwuTm.png) + + * **Git** - We recommend [Git for Windows](https://gitforwindows.org). + + ![3](https://i.imgur.com/UeSzkBw.png) + + * While installing Git Bash, you should tell it to include Git in your system path. (Choose the "Git from the command line and also from 3rd-party software" option.) If you missed that, don't worry, you'll just have to manually tell CMake where your git.exe is, since it's used to include version info into the built executable. + + ![4](https://i.imgur.com/x0rRs1t.png) + +### Cloning Eden with Git + +**Master:** + ```cmd + git clone --recursive https://git.eden-emu.dev/eden-emu/eden + cd eden + ``` + + ![9](https://i.imgur.com/CcxIAht.png) + +* *(Note: eden by default downloads to `C:\Users\\eden` (Master) + +### Building + +* Open the CMake GUI application and point it to the `eden` (Master) + + ![10](https://i.imgur.com/qOslIWv.png) + +* For the build directory, use a `/build` subdirectory inside the source directory or some other directory of your choice. (Tell CMake to create it.) + +* Click the "Configure" button and choose `Visual Studio 17 2022`, with `x64` for the optional platform. + + ![12](https://i.imgur.com/DKiREaK.png) + + * *(Note: If you used GitHub's own app to clone, run `git submodule update --init --recursive` to get the remaining dependencies)* + + * *(You may also want to disable `YUZU_TESTS` in this case since Catch2 is not yet supported with this.)* + + ![13](https://user-images.githubusercontent.com/22451773/180585999-07316d6e-9751-4d11-b957-1cf57cd7cd58.png) + +* Click "Generate" to create the project files. + + ![15](https://i.imgur.com/5LKg92k.png) + +* Open the solution file `yuzu.sln` in Visual Studio 2022, which is located in the build folder. + + ![16](https://i.imgur.com/208yMml.png) + +* Depending if you want a graphical user interface or not (`eden` has the graphical user interface, while `eden-cmd` doesn't), select `eden` or `eden-cmd` in the Solution Explorer, right-click and `Set as StartUp Project`. + + ![17](https://i.imgur.com/nPMajnn.png) ![18](https://i.imgur.com/BDMLzRZ.png) + +* Select the appropriate build type, Debug for debug purposes or Release for performance (in case of doubt choose Release). + + ![19](https://i.imgur.com/qxg4roC.png) + +* Right-click the project you want to build and press Build in the submenu or press F5. + + ![20](https://i.imgur.com/CkQgOFW.png) + +## Method II: MinGW-w64 Build with MSYS2 + +### Prerequisites to install + +* [MSYS2](https://www.msys2.org) +* [Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows) - **Make sure to select Latest SDK.** +* Make sure to follow the instructions and update to the latest version by running `pacman -Syu` as many times as needed. + +### Install eden dependencies for MinGW-w64 + +* Open the `MSYS2 MinGW 64-bit` (mingw64.exe) shell +* Download and install all dependencies using: `pacman -Syu git make mingw-w64-x86_64-SDL2 mingw-w64-x86_64-cmake mingw-w64-x86_64-python-pip mingw-w64-x86_64-qt6 mingw-w64-x86_64-toolchain autoconf libtool automake-wrapper` +* Add MinGW binaries to the PATH: `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc` +* Add glslangValidator to the PATH: `echo 'PATH=$(readlink -e /c/VulkanSDK/*/Bin/):$PATH' >> ~/.bashrc` + +### Clone the eden repository with Git + + ```bash + git clone --recursive https://git.eden-emu.dev/eden-emu/eden + cd eden + ``` + +### Run the following commands to build eden (dynamically linked build) + +```bash +mkdir build && cd build +cmake -G "MSYS Makefiles" -DYUZU_TESTS=OFF .. +make -j$(nproc) +# test eden out with +./bin/eden.exe +``` + +* *(Note: This build is not a static build meaning that you need to include all of the DLLs with the .exe in order to use it!)* + +e.g. +```Bash +cp externals/ffmpeg-*/bin/*.dll bin/ +``` + +Bonus Note: Running programs from inside `MSYS2 MinGW x64` shell has a different %PATH% than directly from explorer. This different %PATH% has the locations of the other DLLs required. +![image](https://user-images.githubusercontent.com/190571/165000848-005e8428-8a82-41b1-bb4d-4ce7797cdac8.png) + + +### Building without Qt (Optional) + +Doesn't require the rather large Qt dependency, but you will lack a GUI frontend: + + * Pass the `-DENABLE_QT=no` flag to cmake + +## Method III: CLion Environment Setup + +### Minimal Dependencies + +To build eden, you need to install the following: + +* [CLion](https://www.jetbrains.com/clion/) - This IDE is not free; for a free alternative, check Method I +* [Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows) - Make sure to select the Latest SDK. + +### Cloning eden with CLion + +* Clone the Repository: + +![1](https://user-images.githubusercontent.com/42481638/216899046-0d41d7d6-8e4d-4ed2-9587-b57088af5214.png) +![2](https://user-images.githubusercontent.com/42481638/216899061-b2ea274a-e88c-40ae-bf0b-4450b46e9fea.png) +![3](https://user-images.githubusercontent.com/42481638/216899076-0e5988c4-d431-4284-a5ff-9ecff973db76.png) + + + +### Building & Setup + +* Once Cloned, You will be taken to a prompt like the image below: + +![4](https://user-images.githubusercontent.com/42481638/216899092-3fe4cec6-a540-44e3-9e1e-3de9c2fffc2f.png) + +* Set the settings to the image below: +* Change `Build type: Release` +* Change `Name: Release` +* Change `Toolchain Visual Studio` +* Change `Generator: Let CMake decide` +* Change `Build directory: build` + +![5](https://user-images.githubusercontent.com/42481638/216899164-6cee8482-3d59-428f-b1bc-e6dc793c9b20.png) + +* Click OK; now Clion will build a directory and index your code to allow for IntelliSense. Please be patient. +* Once this process has been completed (No loading bar bottom right), you can now build eden +* In the top right, click on the drop-down menu, select all configurations, then select eden + +![6](https://user-images.githubusercontent.com/42481638/216899226-975048e9-bc6d-4ec1-bc2d-bd8a1e15ed04.png) + +* Now run by clicking the play button or pressing Shift+F10, and eden will auto-launch once built. + +![7](https://user-images.githubusercontent.com/42481638/216899275-d514ec6a-e563-470e-81e2-3e04f0429b68.png) + +## Building from the command line with MSVC + +```cmd +git clone --recursive https://git.eden-emu.dev/eden-emu/eden +cd eden +mkdir build +cd build +cmake .. -G "Visual Studio 17 2022" -A x64 +cmake --build . --config Release +``` + +### Building with Scripts +A convenience script for building is provided in `.ci/windows/build.sh`. You must run this with Bash, e.g. Git Bash or MinGW TTY. To use this script, you must have windeployqt installed (usually bundled with Qt) and set the `WINDEPLOYQT` environment variable to its canonical Bash location, e.g. `WINDEPLOYQT="/c/Qt/6.9.1/msvc2022_64/bin/windeployqt6.exe" .ci/windows/build.sh`. + +Extra CMake flags should be placed in the arguments of the script. + +Additional environment variables can be used to control building: +- `BUILD_TYPE`: Sets the build type to use. Defaults to `Release` + +The following environment variables are boolean flags. Set to `true` to enable or `false` to disable: +- `DEVEL` (default FALSE): Disable Qt update checker +- `USE_WEBENGINE` (default FALSE): Enable Qt WebEngine +- `USE_MULTIMEDIA` (default TRUE): Enable Qt Multimedia +- `BUNDLE_QT` (default FALSE): Use bundled Qt + * Note that using system Qt requires you to include the Qt CMake directory in `CMAKE_PREFIX_PATH`, e.g. `.ci/windows/build.sh -DCMAKE_PREFIX_PATH=C:/Qt/6.9.0/msvc2022_64/lib/cmake/Qt6` + +After building, a zip can be packaged via `.ci/windows/package.sh`. Note that you must have 7-zip installed and in your PATH. The resulting zip will be placed into `artifacts` in the source directory. diff --git a/docs/build/macOS.md b/docs/build/macOS.md index 6cb62273cb..fd1873b849 100644 --- a/docs/build/macOS.md +++ b/docs/build/macOS.md @@ -1,105 +1,78 @@ -Please note this article is intended for development, and eden on macOS is not currently ready for regular use. - -This article was written for developers. eden support for macOS is not ready for casual use. - -## Method I: ninja ---- -If you are compiling on Intel Mac or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` to `/usr/local`. - -Install dependencies from Homebrew: -```sh -brew install autoconf automake boost ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl2 speexdsp zlib zlib zstd cmake Catch2 molten-vk vulkan-loader -``` - -Clone the repo -```sh -git clone --recursive https://git.eden-emu.dev/eden-emu/eden - -cd eden -``` - -Build for release -```sh -mkdir build && cd build - -export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" - -export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib - -cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_USE_BUNDLED_VCPKG=OFF -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=ON -DENABLE_LIBUSB=OFF -DCLANG_FORMAT=ON -DSDL2_DISABLE_INSTALL=ON -DSDL_ALTIVEC=ON - -ninja -``` - -You may also want to include support for Discord Rich Presence by adding `-DUSE_DISCORD_PRESENCE=ON` after `cmake ..` - -Build with debug symbols (vcpkg is not currently used due to broken boost-context library): -```sh -mkdir build && cd build -export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" -cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_USE_BUNDLED_VCPKG=OFF -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF -ninja -``` - -Run the output: -``` -bin/eden.app/Contents/MacOS/eden -``` - -## Method II: Xcode - ---- -If you are compiling on Intel Mac or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` to `/usr/local`. - -Install dependencies from Homebrew: -```sh -brew install autoconf automake boost ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl2 speexdsp zlib zlib zstd cmake Catch2 molten-vk vulkan-loader -``` - -Clone the repo -```sh -git clone --recursive https://git.eden-emu.dev/eden-emu/eden - -cd eden -``` - -Build for release -```sh -mkdir build && cd build - -export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" - -export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib - -cmake .. -GXcode -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_USE_BUNDLED_VCPKG=OFF -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=ON -DENABLE_LIBUSB=OFF -DCLANG_FORMAT=ON -DSDL2_DISABLE_INSTALL=ON -DSDL_ALTIVEC=ON - -xcodebuild build -project eden.xcodeproj -scheme "eden" -configuration "RelWithDebInfo" -``` - -You may also want to include support for Discord Rich Presence by adding `-DUSE_DISCORD_PRESENCE=ON` after `cmake ..` - -Build with debug symbols (vcpkg is not currently used due to broken boost-context library): -```sh -mkdir build && cd build -export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" -cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_USE_BUNDLED_VCPKG=OFF -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF -ninja -``` - -Run the output: -``` -bin/eden.app/Contents/MacOS/eden -``` - ---- - -To run with MoltenVK, install additional dependencies: -```sh -brew install molten-vk vulkan-loader -``` - -Run with Vulkan loader path: -```sh -export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib -bin/eden.app/Contents/MacOS/eden -``` \ No newline at end of file +Please note this article is intended for development, and Eden on macOS is not currently ready for regular use. + +This article was written for developers. Eden support for macOS is not ready for casual use. + +## Dependencies +Install dependencies from Homebrew: +```sh +brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl2 speexdsp zlib zstd cmake Catch2 molten-vk vulkan-loader spirv-tools +``` + +If you are compiling on Intel Mac, or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` with `/usr/local`. + +Now, clone the repo: +```sh +git clone --recursive https://git.eden-emu.dev/eden-emu/eden +cd eden +``` + +## Method I: ninja + +--- +Build for release +```sh +export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" +export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib +cmake -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=ON -DENABLE_LIBUSB=OFF -DCLANG_FORMAT=ON -DSDL2_DISABLE_INSTALL=ON -DSDL_ALTIVEC=ON +ninja +``` + +You may also want to include support for Discord Rich Presence by adding `-DUSE_DISCORD_PRESENCE=ON` +```sh +export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" +cmake -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF +ninja +``` + +Run the output: +``` +bin/eden.app/Contents/MacOS/eden +``` + +## Method II: Xcode + +--- +Build for release +```sh +export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" +export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib +# Only if having errors about Xcode 15.0 +sudo /usr/bin/xcode-select --switch /Users/admin/Downloads/Xcode.ap +cmake -B build -GXcode -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=ON -DENABLE_LIBUSB=OFF -DCLANG_FORMAT=ON -DSDL2_DISABLE_INSTALL=ON -DSDL_ALTIVEC=ON +xcodebuild build -project yuzu.xcodeproj -scheme "yuzu" -configuration "RelWithDebInfo" +``` + +Build with debug symbols: +```sh +export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" +cmake -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF +ninja +``` + +Run the output: +``` +bin/eden.app/Contents/MacOS/eden +``` + +--- + +To run with MoltenVK, install additional dependencies: +```sh +brew install molten-vk vulkan-loader +``` + +Run with Vulkan loader path: +```sh +export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib +bin/eden.app/Contents/MacOS/eden +``` diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index b209b48db9..d613201566 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2025 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later @@ -33,7 +36,7 @@ endif() # Xbyak (also used by Dynarmic, so needs to be added first) if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) - if (PLATFORM_SUN) + if (PLATFORM_SUN OR PLATFORM_OPENBSD) AddJsonPackage(xbyak_sun) else() AddJsonPackage(xbyak) @@ -67,7 +70,43 @@ if (mbedtls_ADDED) endif() # libusb +# TODO(crueter): remove entirely, replace with cpm if (ENABLE_LIBUSB AND NOT TARGET libusb::usb) + set(module externals/libusb) + + file(GLOB RESULT "${PROJECT_SOURCE_DIR}/${module}/") + list(LENGTH RESULT RES_LEN) + if(RES_LEN EQUAL 0) + message(FATAL_ERROR "libusb not found. " + "Please run: \ngit submodule update --init") + endif() + + if (EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git") + set(SUBMODULE_DIR "${PROJECT_SOURCE_DIR}/${module}") + + execute_process( + COMMAND git rev-parse --short=10 HEAD + WORKING_DIRECTORY ${SUBMODULE_DIR} + OUTPUT_VARIABLE SUBMODULE_SHA + ) + + # would probably be better to do string parsing, but whatever + execute_process( + COMMAND git remote get-url origin + WORKING_DIRECTORY ${SUBMODULE_DIR} + OUTPUT_VARIABLE SUBMODULE_URL + ) + + string(REGEX REPLACE "\n|\r" "" SUBMODULE_SHA ${SUBMODULE_SHA}) + string(REGEX REPLACE "\n|\r|\\.git" "" SUBMODULE_URL ${SUBMODULE_URL}) + + get_filename_component(SUBMODULE_NAME ${SUBMODULE_DIR} NAME) + + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_NAMES ${SUBMODULE_NAME}) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS ${SUBMODULE_SHA}) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_URLS ${SUBMODULE_URL}) + endif() + add_subdirectory(libusb) endif() @@ -96,15 +135,7 @@ if (ENABLE_WEB_SERVICE) endif() # unordered_dense -AddPackage( - NAME unordered_dense - REPO "Lizzie841/unordered_dense" - SHA e59d30b7b1 - HASH 71eff7bd9ba4b9226967bacd56a8ff000946f8813167cb5664bb01e96fb79e4e220684d824fe9c59c4d1cc98c606f13aff05b7940a1ed8ab3c95d6974ee34fa0 - FIND_PACKAGE_ARGUMENTS "CONFIG" - OPTIONS - "UNORDERED_DENSE_INSTALL OFF" -) +AddJsonPackage(unordered-dense) # FFMpeg if (YUZU_USE_BUNDLED_FFMPEG) @@ -115,21 +146,17 @@ if (YUZU_USE_BUNDLED_FFMPEG) set(FFmpeg_INCLUDE_DIR "${FFmpeg_INCLUDE_DIR}" PARENT_SCOPE) endif() -# Vulkan-Headers - -# TODO(crueter): Vk1.4 impl - +# VulkanUtilityHeaders - pulls in headers and utility libs AddJsonPackage( - NAME vulkan-headers - BUNDLED_PACKAGE ${YUZU_USE_EXTERNAL_VULKAN_HEADERS} -) - -# Vulkan-Utility-Libraries -AddJsonPackage( - NAME vulkan-utility-libraries + NAME vulkan-utility-headers BUNDLED_PACKAGE ${YUZU_USE_EXTERNAL_VULKAN_UTILITY_LIBRARIES} ) +# small hack +if (NOT VulkanUtilityLibraries_ADDED) + find_package(VulkanHeaders 1.3.274 REQUIRED) +endif() + # SPIRV Tools AddJsonPackage( NAME spirv-tools @@ -147,6 +174,10 @@ add_subdirectory(nx_tzdb) # VMA AddJsonPackage(vulkan-memory-allocator) +if (VulkanMemoryAllocator_ADDED AND MSVC) + target_compile_options(VulkanMemoryAllocator INTERFACE /wd4189) +endif() + if (NOT TARGET LLVM::Demangle) add_library(demangle demangle/ItaniumDemangle.cpp) target_include_directories(demangle PUBLIC ./demangle) @@ -243,7 +274,7 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client) file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES ${breakpad_SOURCE_DIR}/src/client/mac/*.cc ${breakpad_SOURCE_DIR}/src/common/mac/*.cc) list(APPEND LIBBREAKPAD_CLIENT_SOURCES ${breakpad_SOURCE_DIR}/src/common/mac/MachIPC.mm) else() - target_compile_definitions(libbreakpad_client PUBLIC -DHAVE_A_OUT_H) + target_compile_definitions(libbreakpad_client PUBLIC HAVE_A_OUT_H) file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES ${breakpad_SOURCE_DIR}/src/client/linux/*.cc ${breakpad_SOURCE_DIR}/src/common/linux/*.cc) endif() list(APPEND LIBBREAKPAD_CLIENT_SOURCES ${LIBBREAKPAD_COMMON_SOURCES}) diff --git a/externals/cpmfile.json b/externals/cpmfile.json index effcbcc01f..f8ca528951 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -3,6 +3,7 @@ "repo": "Mbed-TLS/mbedtls", "sha": "8c88150ca1", "hash": "769ad1e94c570671071e1f2a5c0f1027e0bf6bcdd1a80ea8ac970f2c86bc45ce4e31aa88d6d8110fc1bed1de81c48bc624df1b38a26f8b340a44e109d784a966", + "find_args": "MODULE", "patches": [ "0001-cmake-version.patch" ] @@ -42,18 +43,13 @@ "0002-missing-decl.patch" ] }, - "vulkan-headers": { - "package": "VulkanHeaders", - "version": "1.3.274", - "repo": "KhronosGroup/Vulkan-Headers", - "sha": "89268a6d17", - "hash": "3ab349f74298ba72cafb8561015690c0674d428a09fb91ccd3cd3daca83650d190d46d33fd97b0a8fd4223fe6df2bcabae89136fbbf7c0bfeb8776f9448304c8" - }, - "vulkan-utility-libraries": { + "vulkan-utility-headers": { "package": "VulkanUtilityLibraries", - "repo": "KhronosGroup/Vulkan-Utility-Libraries", - "sha": "df2e358152", - "hash": "3e468c3d9ff93f6d418d71e5527abe0a12c8c7ab5b0b52278bbbee4d02bb87e99073906729b727e0147242b7e3fd5dedf68b803f1878cb4c0e4f730bc2238d79" + "repo": "scripts/VulkanUtilityHeaders", + "tag": "1.4.326", + "artifact": "VulkanUtilityHeaders.tar.zst", + "git_host": "git.crueter.xyz", + "hash": "5924629755cb1605c4aa4eee20ef7957a9dd8d61e4df548be656d98054f2730c4109693c1bd35811f401f4705d2ccff9fc849be32b0d8480bc3f73541a5e0964" }, "vulkan-memory-allocator": { "package": "VulkanMemoryAllocator", @@ -74,14 +70,14 @@ }, "xbyak_sun": { "package": "xbyak", - "repo": "Lizzie841/xbyak", - "sha": "51f507b0b3", - "hash": "4a29a3c2f97f7d5adf667a21a008be03c951fb6696b0d7ba27e7e4afa037bc76eb5e059bb84860e01baf741d4d3ac851b840cd54c99d038812fbe0f1fa6d38a4", + "repo": "herumi/xbyak", + "sha": "9bb219333a", + "hash": "303165d45c8c19387ec49d9fda7d7a4e0d86d4c0153898c23f25ce2d58ece567f44c0bbbfe348239b933edb6e1a1e34f4bc1c0ab3a285bee5da0e548879387b0", "bundled": true }, "xbyak": { "package": "xbyak", - "repo": "Lizzie841/xbyak", + "repo": "herumi/xbyak", "sha": "4e44f4614d", "hash": "5824e92159e07fa36a774aedd3b3ef3541d0241371d522cffa4ab3e1f215fa5097b1b77865b47b2481376c704fa079875557ea463ca63d0a7fd6a8a20a589e70", "bundled": true @@ -105,5 +101,18 @@ "sha": "2bc873e53c", "hash": "02329058a7f9cf7d5039afaae5ab170d9f42f60f4c01e21eaf4f46073886922b057a9ae30eeac040b3ac182f51b9c1bfe9fe1050a2c9f6ce567a1a9a0ec2c768", "bundled": true + }, + "unordered-dense": { + "package": "unordered_dense", + "repo": "martinus/unordered_dense", + "sha": "73f3cbb237", + "hash": "c08c03063938339d61392b687562909c1a92615b6ef39ec8df19ea472aa6b6478e70d7d5e33d4a27b5d23f7806daf57fe1bacb8124c8a945c918c7663a9e8532", + "find_args": "CONFIG", + "options": [ + "UNORDERED_DENSE_INSTALL OFF" + ], + "patches": [ + "0001-cmake.patch" + ] } } diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 54c852f831..ff35c8dc2c 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -63,20 +63,22 @@ if (NOT WIN32 AND NOT ANDROID) set(FFmpeg_HWACCEL_INCLUDE_DIRS) set(FFmpeg_HWACCEL_LDFLAGS) - # In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so - if(PLATFORM_SUN) - list(APPEND FFmpeg_HWACCEL_LIBRARIES - X11 - "/usr/lib/xorg/amd64/libdrm.so") - else() - pkg_check_modules(LIBDRM libdrm REQUIRED) - list(APPEND FFmpeg_HWACCEL_LIBRARIES - ${LIBDRM_LIBRARIES}) - list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS - ${LIBDRM_INCLUDE_DIRS}) + if (NOT APPLE) + # In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so + if(PLATFORM_SUN) + list(APPEND FFmpeg_HWACCEL_LIBRARIES + X11 + "/usr/lib/xorg/amd64/libdrm.so") + else() + pkg_check_modules(LIBDRM libdrm REQUIRED) + list(APPEND FFmpeg_HWACCEL_LIBRARIES + ${LIBDRM_LIBRARIES}) + list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS + ${LIBDRM_INCLUDE_DIRS}) + endif() + list(APPEND FFmpeg_HWACCEL_FLAGS + --enable-libdrm) endif() - list(APPEND FFmpeg_HWACCEL_FLAGS - --enable-libdrm) if(LIBVA_FOUND) find_package(X11 REQUIRED) diff --git a/externals/nx_tzdb/cpmfile.json b/externals/nx_tzdb/cpmfile.json index fc7dd77628..feb9daf7da 100644 --- a/externals/nx_tzdb/cpmfile.json +++ b/externals/nx_tzdb/cpmfile.json @@ -1,7 +1,10 @@ { "tzdb": { "package": "nx_tzdb", - "url": "https://github.com/crueter/tzdb_to_nx/releases/download/250725/250725.zip", + "repo": "misc/tzdb_to_nx", + "git_host": "git.crueter.xyz", + "artifact": "%VERSION%.zip", + "tag": "%VERSION%", "hash": "8f60b4b29f285e39c0443f3d5572a73780f3dbfcfd5b35004451fadad77f3a215b2e2aa8d0fffe7e348e2a7b0660882b35228b6178dda8804a14ce44509fd2ca", "version": "250725" } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bd1285b2bc..b1fbab6a59 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,16 +22,16 @@ if (MSVC) set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) # Silence "deprecation" warnings - add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE _SCL_SECURE_NO_WARNINGS) # Avoid windows.h junk - add_definitions(-DNOMINMAX) + add_compile_definitions(NOMINMAX) # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors. - add_definitions(-DWIN32_LEAN_AND_MEAN) + add_compile_definitions(WIN32_LEAN_AND_MEAN) # Ensure that projects are built with Unicode support. - add_definitions(-DUNICODE -D_UNICODE) + add_compile_definitions(UNICODE _UNICODE) # /W4 - Level 4 warnings # /MP - Multi-threaded compilation @@ -169,15 +169,15 @@ else() # glibc, which may default to 32 bits. glibc allows this to be configured # by setting _FILE_OFFSET_BITS. if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) - add_definitions(-D_FILE_OFFSET_BITS=64) + add_compile_definitions(_FILE_OFFSET_BITS=64) endif() if (MINGW) - add_definitions(-DMINGW_HAS_SECURE_API) + add_compile_definitions(MINGW_HAS_SECURE_API) add_compile_options("-msse4.1") if (MINGW_STATIC_BUILD) - add_definitions(-DQT_STATICPLUGIN) + add_compile_definitions(QT_STATICPLUGIN) add_compile_options("-static") endif() endif() diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index d907284bb7..c76b5e7162 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -30,8 +30,8 @@ val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toIn android { namespace = "org.yuzu.yuzu_emu" - compileSdkVersion = "android-35" - ndkVersion = "26.1.10909125" + compileSdkVersion = "android-36" + ndkVersion = "28.2.13676358" buildFeatures { viewBinding = true @@ -173,12 +173,14 @@ android { "-DENABLE_OPENSSL=ON", "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-DYUZU_USE_CPM=ON", + "-DCPMUTIL_FORCE_BUNDLED=ON", "-DYUZU_USE_BUNDLED_FFMPEG=ON", "-DYUZU_ENABLE_LTO=ON", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-DBUILD_TESTING=OFF", "-DYUZU_TESTS=OFF", - "-DDYNARMIC_TESTS=OFF" + "-DDYNARMIC_TESTS=OFF", + "-DDYNARMIC_ENABLE_LTO=ON" ) abiFilters("arm64-v8a") diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 11b81a01a6..98f342c274 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -36,6 +36,9 @@ import androidx.core.net.toUri import androidx.core.content.edit import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting +import org.yuzu.yuzu_emu.features.settings.model.Settings +import org.yuzu.yuzu_emu.utils.NativeConfig class GameAdapter(private val activity: AppCompatActivity) : AbstractDiffAdapter(exact = false) { @@ -229,6 +232,8 @@ class GameAdapter(private val activity: AppCompatActivity) : binding.root.findNavController().navigate(action) } + val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + if (NativeLibrary.gameRequiresFirmware(game.programId) && !NativeLibrary.isFirmwareAvailable()) { MaterialAlertDialogBuilder(activity) .setTitle(R.string.loader_requires_firmware) @@ -243,6 +248,23 @@ class GameAdapter(private val activity: AppCompatActivity) : } .setNegativeButton(android.R.string.cancel) { _, _ -> } .show() + } else if (BooleanSetting.DISABLE_NCA_VERIFICATION.getBoolean(false) && !preferences.getBoolean( + Settings.PREF_HIDE_NCA_POPUP, false)) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.nca_verification_disabled) + .setMessage(activity.getString(R.string.nca_verification_disabled_description)) + .setPositiveButton(android.R.string.ok) { _, _ -> + launch() + } + .setNeutralButton(R.string.dont_show_again) { _, _ -> + preferences.edit { + putBoolean(Settings.PREF_HIDE_NCA_POPUP, true) + } + + launch() + } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .show() } else { launch() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 3c5b9003de..6d4bfd97ac 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -35,6 +35,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_SAMPLE_SHADING("sample_shading"), PICTURE_IN_PICTURE("picture_in_picture"), USE_CUSTOM_RTC("custom_rtc_enabled"), + DISABLE_NCA_VERIFICATION("disable_nca_verification"), BLACK_BACKGROUNDS("black_backgrounds"), JOYSTICK_REL_CENTER("joystick_rel_center"), DPAD_SLIDE("dpad_slide"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index a52f582031..2564849ef4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -37,6 +37,7 @@ object Settings { const val PREF_SHOULD_SHOW_PRE_ALPHA_WARNING = "ShouldShowPreAlphaWarning" const val PREF_SHOULD_SHOW_EDENS_VEIL_DIALOG = "ShouldShowEdensVeilDialog" const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" + const val PREF_HIDE_NCA_POPUP = "HideNCAVerificationPopup" const val SECTION_STATS_OVERLAY = "Stats Overlay" // Deprecated input overlay preference keys diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index a689b6ce76..883d8efaef 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -297,7 +297,13 @@ abstract class SettingsItem( descriptionId = R.string.use_custom_rtc_description ) ) - + put( + SwitchSetting( + BooleanSetting.DISABLE_NCA_VERIFICATION, + titleId = R.string.disable_nca_verification, + descriptionId = R.string.disable_nca_verification_description + ) + ) put( StringInputSetting( StringSetting.WEB_TOKEN, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 14d62ceec3..630bcb0d74 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -210,6 +210,7 @@ class SettingsFragmentPresenter( add(IntSetting.LANGUAGE_INDEX.key) add(BooleanSetting.USE_CUSTOM_RTC.key) add(LongSetting.CUSTOM_RTC.key) + add(BooleanSetting.DISABLE_NCA_VERIFICATION.key) add(HeaderSetting(R.string.network)) add(StringSetting.WEB_TOKEN.key) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt index b8d0f2197e..dea762dc17 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt @@ -79,7 +79,7 @@ class DriverFetcherFragment : Fragment() { IntRange(600, 639) to "Mr. Purple EOL-24.3.4", IntRange(640, 699) to "Mr. Purple T19", IntRange(700, 710) to "KIMCHI 25.2.0_r5", - IntRange(711, 799) to "Mr. Purple T21", + IntRange(711, 799) to "Mr. Purple T22", IntRange(800, 899) to "GameHub Adreno 8xx", IntRange(900, Int.MAX_VALUE) to "Unsupported" ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 96015e58ec..5cc912fbbe 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -509,6 +509,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { gpuModel = GpuDriverHelper.getGpuModel().toString() fwVersion = NativeLibrary.firmwareVersion() + updateQuickOverlayMenuEntry(BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) + binding.surfaceEmulation.holder.addCallback(this) binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } @@ -530,6 +532,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) binding.inGameMenu.requestFocus() emulationViewModel.setDrawerOpen(true) + updateQuickOverlayMenuEntry(BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) } override fun onDrawerClosed(drawerView: View) { @@ -571,25 +574,24 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { R.id.menu_pause_emulation -> { if (emulationState.isPaused) { emulationState.run(false) - it.title = resources.getString(R.string.emulation_pause) - it.icon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_pause, - requireContext().theme - ) + updatePauseMenuEntry(false) } else { emulationState.pause() - it.title = resources.getString(R.string.emulation_unpause) - it.icon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_play, - requireContext().theme - ) + updatePauseMenuEntry(true) } binding.inGameMenu.requestFocus() true } + R.id.menu_quick_overlay -> { + val newState = !BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() + BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState) + updateQuickOverlayMenuEntry(newState) + binding.surfaceInputOverlay.refreshControls() + NativeConfig.saveGlobalConfig() + true + } + R.id.menu_settings -> { val action = HomeNavigationDirections.actionGlobalSettingsActivity( null, @@ -844,9 +846,50 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + private fun updateQuickOverlayMenuEntry(isVisible: Boolean) { + val menu = binding.inGameMenu.menu + val item = menu.findItem(R.id.menu_quick_overlay) + if (isVisible) { + item.title = getString(R.string.emulation_hide_overlay) + item.icon = ResourcesCompat.getDrawable( + resources, + R.drawable.ic_controller_disconnected, + requireContext().theme + ) + } else { + item.title = getString(R.string.emulation_show_overlay) + item.icon = ResourcesCompat.getDrawable( + resources, + R.drawable.ic_controller, + requireContext().theme + ) + } + } + + private fun updatePauseMenuEntry(isPaused: Boolean) { + val menu = binding.inGameMenu.menu + val pauseItem = menu.findItem(R.id.menu_pause_emulation) + if (isPaused) { + pauseItem.title = getString(R.string.emulation_unpause) + pauseItem.icon = ResourcesCompat.getDrawable( + resources, + R.drawable.ic_play, + requireContext().theme + ) + } else { + pauseItem.title = getString(R.string.emulation_pause) + pauseItem.icon = ResourcesCompat.getDrawable( + resources, + R.drawable.ic_pause, + requireContext().theme + ) + } + } + override fun onPause() { if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { emulationState.pause() + updatePauseMenuEntry(true) } super.onPause() } @@ -869,6 +912,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { val socPosition = IntSetting.SOC_OVERLAY_POSITION.getInt() updateSocPosition(socPosition) + + binding.inGameMenu.post { + emulationState?.isPaused?.let { updatePauseMenuEntry(it) } + } } private fun resetInputOverlay() { @@ -1391,6 +1438,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { R.id.menu_show_overlay -> { it.isChecked = !it.isChecked BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked) + updateQuickOverlayMenuEntry(it.isChecked) binding.surfaceInputOverlay.refreshControls() true } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index 97a60ee184..a06abb394f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt @@ -31,9 +31,6 @@ class HomeViewModel : ViewModel() { private val _checkKeys = MutableStateFlow(false) val checkKeys = _checkKeys.asStateFlow() - private val _checkFirmware = MutableStateFlow(false) - val checkFirmware = _checkFirmware.asStateFlow() - var navigatedToSetup = false fun setStatusBarShadeVisibility(visible: Boolean) { @@ -66,8 +63,4 @@ class HomeViewModel : ViewModel() { fun setCheckKeys(value: Boolean) { _checkKeys.value = value } - - fun setCheckFirmware(value: Boolean) { - _checkFirmware.value = value - } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index fffaa1e3ba..cfed4d08ec 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -38,6 +38,7 @@ import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.InstallResult +import android.os.Build import org.yuzu.yuzu_emu.model.TaskState import org.yuzu.yuzu_emu.model.TaskViewModel import org.yuzu.yuzu_emu.utils.* @@ -47,6 +48,7 @@ import java.io.BufferedOutputStream import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import androidx.core.content.edit +import kotlin.text.compareTo class MainActivity : AppCompatActivity(), ThemeProvider { private lateinit var binding: ActivityMainBinding @@ -110,6 +112,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider { binding = ActivityMainBinding.inflate(layoutInflater) + // Since Android 15, google automatically forces "games" to be 60 hrz + // This ensures the display's max refresh rate is actually used + display?.let { + val supportedModes = it.supportedModes + val maxRefreshRate = supportedModes.maxByOrNull { mode -> mode.refreshRate } + + if (maxRefreshRate != null) { + val layoutParams = window.attributes + layoutParams.preferredDisplayModeId = maxRefreshRate.modeId + window.attributes = layoutParams + } + } + setContentView(binding.root) checkAndRequestBluetoothPermissions() @@ -127,16 +142,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { checkedDecryption = true } - if (!checkedFirmware) { - val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true) - if (!firstTimeSetup) { - checkFirmware() - showPreAlphaWarningDialog() - } - checkedFirmware = true - } - WindowCompat.setDecorFitsSystemWindows(window, false) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) @@ -183,13 +188,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { if (it) checkKeys() } - homeViewModel.checkFirmware.collect( - this, - resetState = { homeViewModel.setCheckFirmware(false) } - ) { - if (it) checkFirmware() - } - setInsets() } @@ -228,21 +226,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { ).show(supportFragmentManager, MessageDialogFragment.TAG) } } - - private fun checkFirmware() { - val resultCode: Int = NativeLibrary.verifyFirmware() - if (resultCode == 0) return - - val resultString: String = - resources.getStringArray(R.array.verifyFirmwareResults)[resultCode] - - MessageDialogFragment.newInstance( - titleId = R.string.firmware_invalid, - descriptionString = resultString, - helpLinkId = R.string.firmware_missing_help - ).show(supportFragmentManager, MessageDialogFragment.TAG) - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption) @@ -419,7 +402,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { cacheFirmwareDir.copyRecursively(firmwarePath, true) NativeLibrary.initializeSystem(true) homeViewModel.setCheckKeys(true) - homeViewModel.setCheckFirmware(true) getString(R.string.save_file_imported_success) } } catch (e: Exception) { @@ -449,7 +431,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { // Optionally reinitialize the system or perform other necessary steps NativeLibrary.initializeSystem(true) homeViewModel.setCheckKeys(true) - homeViewModel.setCheckFirmware(true) messageToShow = getString(R.string.firmware_uninstalled_success) } else { messageToShow = getString(R.string.firmware_uninstalled_failure) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt index a317be14d5..377313d0aa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt @@ -124,11 +124,16 @@ object CustomSettingsHandler { // Check for driver requirements if activity and driverViewModel are provided if (activity != null && driverViewModel != null) { - val driverPath = extractDriverPath(customSettings) - if (driverPath != null) { - Log.info("[CustomSettingsHandler] Custom settings specify driver: $driverPath") + val rawDriverPath = extractDriverPath(customSettings) + if (rawDriverPath != null) { + // Normalize to local storage path (we only store drivers under driverStoragePath) + val driverFilename = rawDriverPath.substringAfterLast('/') + .substringAfterLast('\\') + val localDriverPath = "${GpuDriverHelper.driverStoragePath}$driverFilename" + Log.info("[CustomSettingsHandler] Custom settings specify driver: $rawDriverPath (normalized: $localDriverPath)") + // Check if driver exists in the driver storage - val driverFile = File(driverPath) + val driverFile = File(localDriverPath) if (!driverFile.exists()) { Log.info("[CustomSettingsHandler] Driver not found locally: ${driverFile.name}") @@ -182,7 +187,7 @@ object CustomSettingsHandler { } // Attempt to download and install the driver - val driverUri = DriverResolver.ensureDriverAvailable(driverPath, activity) { progress -> + val driverUri = DriverResolver.ensureDriverAvailable(driverFilename, activity) { progress -> progressChannel.trySend(progress.toInt()) } @@ -209,12 +214,12 @@ object CustomSettingsHandler { return null } - // Verify the downloaded driver - val installedFile = File(driverPath) + // Verify the downloaded driver (from normalized local path) + val installedFile = File(localDriverPath) val metadata = GpuDriverHelper.getMetadataFromZip(installedFile) if (metadata.name == null) { Log.error( - "[CustomSettingsHandler] Downloaded driver is invalid: $driverPath" + "[CustomSettingsHandler] Downloaded driver is invalid: $localDriverPath" ) Toast.makeText( activity, @@ -232,7 +237,7 @@ object CustomSettingsHandler { } // Add to driver list - driverViewModel.onDriverAdded(Pair(driverPath, metadata)) + driverViewModel.onDriverAdded(Pair(localDriverPath, metadata)) Log.info( "[CustomSettingsHandler] Successfully downloaded and installed driver: ${metadata.name}" ) @@ -268,7 +273,7 @@ object CustomSettingsHandler { // Driver exists, verify it's valid val metadata = GpuDriverHelper.getMetadataFromZip(driverFile) if (metadata.name == null) { - Log.error("[CustomSettingsHandler] Invalid driver file: $driverPath") + Log.error("[CustomSettingsHandler] Invalid driver file: $localDriverPath") Toast.makeText( activity, activity.getString( @@ -459,6 +464,8 @@ object CustomSettingsHandler { if (inGpuDriverSection && trimmed.startsWith("driver_path=")) { return trimmed.substringAfter("driver_path=") + .trim() + .removeSurrounding("\"", "\"") } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt index 74f98ccbd2..2072344bdf 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt @@ -68,6 +68,48 @@ object DriverResolver { val filename: String ) + // Matching helpers + private val KNOWN_SUFFIXES = listOf( + ".adpkg.zip", + ".zip", + ".7z", + ".tar.gz", + ".tar.xz", + ".rar" + ) + + private fun stripKnownSuffixes(name: String): String { + var result = name + var changed: Boolean + do { + changed = false + for (s in KNOWN_SUFFIXES) { + if (result.endsWith(s, ignoreCase = true)) { + result = result.dropLast(s.length) + changed = true + } + } + } while (changed) + return result + } + + private fun normalizeName(name: String): String { + val base = stripKnownSuffixes(name.lowercase()) + // Remove non-alphanumerics to make substring checks resilient + return base.replace(Regex("[^a-z0-9]+"), " ").trim() + } + + private fun tokenize(name: String): Set = + normalizeName(name).split(Regex("\\s+")).filter { it.isNotBlank() }.toSet() + + // Jaccard similarity between two sets + private fun jaccard(a: Set, b: Set): Double { + if (a.isEmpty() || b.isEmpty()) return 0.0 + val inter = a.intersect(b).size.toDouble() + val uni = a.union(b).size.toDouble() + return if (uni == 0.0) 0.0 else inter / uni + } + /** * Resolve a driver download URL from its filename * @param filename The driver filename (e.g., "turnip_mrpurple-T19-toasted.adpkg.zip") @@ -98,7 +140,7 @@ object DriverResolver { async { searchRepository(repoPath, filename) } - }.mapNotNull { it.await() }.firstOrNull().also { resolved -> + }.firstNotNullOfOrNull { it.await() }.also { resolved -> // Cache the result if found resolved?.let { urlCache[filename] = it @@ -119,22 +161,56 @@ object DriverResolver { releaseCache[repoPath] = it } - // Search through all releases and artifacts + // First pass: exact name (case-insensitive) against asset filenames + val target = filename.lowercase() for (release in releases) { for (artifact in release.artifacts) { - if (artifact.name == filename) { - Log.info( - "[DriverResolver] Found $filename in $repoPath/${release.tagName}" - ) + if (artifact.name.equals(filename, ignoreCase = true) || artifact.name.lowercase() == target) { + Log.info("[DriverResolver] Found $filename in $repoPath/${release.tagName}") return@withContext ResolvedDriver( downloadUrl = artifact.url.toString(), repoPath = repoPath, releaseTag = release.tagName, - filename = filename + filename = artifact.name ) } } } + + // Second pass: fuzzy match by asset filenames only + val reqNorm = normalizeName(filename) + val reqTokens = tokenize(filename) + var best: ResolvedDriver? = null + var bestScore = 0.0 + + for (release in releases) { + for (artifact in release.artifacts) { + val artNorm = normalizeName(artifact.name) + val artTokens = tokenize(artifact.name) + + var score = jaccard(reqTokens, artTokens) + // Boost if one normalized name contains the other + if (artNorm.contains(reqNorm) || reqNorm.contains(artNorm)) { + score = maxOf(score, 0.92) + } + + if (score > bestScore) { + bestScore = score + best = ResolvedDriver( + downloadUrl = artifact.url.toString(), + repoPath = repoPath, + releaseTag = release.tagName, + filename = artifact.name + ) + } + } + } + + // Threshold to avoid bad guesses, this worked fine in testing but might need tuning + if (best != null && bestScore >= 0.6) { + Log.info("[DriverResolver] Fuzzy matched $filename -> ${best.filename} in ${best.repoPath} (score=%.2f)".format(bestScore)) + return@withContext best + } null } catch (e: Exception) { Log.error("[DriverResolver] Failed to search $repoPath: ${e.message}") @@ -296,8 +372,8 @@ object DriverResolver { context: Context, onProgress: ((Float) -> Unit)? = null ): Uri? { - // Extract filename from path - val filename = driverPath.substringAfterLast('/') + // Extract filename from path (support both separators) + val filename = driverPath.substringAfterLast('/').substringAfterLast('\\') // Check if driver already exists locally val localPath = "${GpuDriverHelper.driverStoragePath}$filename" diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index 1e30b16d96..9dbee1fcef 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(yuzu-android SHARED set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) -target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common Vulkan::Headers) +target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common Vulkan::Headers GPUOpen::VulkanMemoryAllocator) target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log) if (ARCHITECTURE_arm64) target_link_libraries(yuzu-android PRIVATE adrenotools) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index f8f175d313..306b7e2a4c 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -596,6 +596,8 @@ jstring Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_getGpuModel(JNIEnv *env, j const std::string model_name{device.GetModelName()}; + window.release(); + return Common::Android::ToJString(env, model_name); } diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml index ce3f11f6e6..d45699638c 100644 --- a/src/android/app/src/main/res/menu/menu_in_game.xml +++ b/src/android/app/src/main/res/menu/menu_in_game.xml @@ -8,6 +8,11 @@ android:icon="@drawable/ic_pause" android:title="@string/emulation_pause" /> + + ساعة مخصصة في الوقت الحقيقي يسمح لك بتعيين ساعة مخصصة في الوقت الفعلي منفصلة عن وقت النظام الحالي لديك تعيين ساعة مخصصة في الوقت الحقيقي + تعطيل التحقق من NCA + يعطل التحقق من سلامة أرشيفات محتوى NCA. قد يحسن هذا من سرعة التحميل لكنه يخاطر بتلف البيانات أو تمرير ملفات غير صالحة دون اكتشاف. ضروري لجعل الألعاب والتحديثات التي تتطلب نظامًا أساسيًا 20+ تعمل. توليد @@ -733,7 +735,8 @@ مركز العصا النسبي مزلاق الأسهم الاهتزازات الديناميكية - عرض التراكب + إظهار وحدة التحكم + إخفاء وحدة التحكم الكل ضبط التراكب الحجم diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml index 221a197843..af0eeeaa45 100644 --- a/src/android/app/src/main/res/values-ckb/strings.xml +++ b/src/android/app/src/main/res/values-ckb/strings.xml @@ -482,6 +482,8 @@ RTCی تایبەتمەند ڕێگەت پێدەدات کاتژمێرێکی کاتی ڕاستەقینەی تایبەتمەند دابنێیت کە جیاوازە لە کاتی ئێستای سیستەمەکەت. دانانی RTCی تایبەتمەند + ناچالاککردنی پشکنینی NCA + پشکنینی پێکهاتەی ئارشیڤەکانی ناوەڕۆکی NCA ناچالاک دەکات. ئەمە لەوانەیە خێرایی بارکردن به‌ره‌وپێش ببات، بەڵام مەترسی لەناوچوونی داتا یان ئەوەی فایلە نادروستەکان بەبێ ئەوەی دۆزرایەوە تێپەڕبن زیاتر دەکات. بۆ ئەوەی یاری و نوێکردنەوەکان کار بکەن کە پێویستی بە فریموێری 20+ هەیە زۆر پێویستە. بەرهەم هێنان @@ -710,7 +712,8 @@ ناوەندی گێڕ بەنزیکەیی خلیسکانی 4 دوگمەکە لەرینەوەی پەنجەلێدان - نیشاندانی داپۆشەر + نیشاندانی کۆنتڕۆڵەر + پیشاندانی کۆنتڕۆڵەر گۆڕینی سەرجەم ڕێکخستنی داپۆشەر پێوەر diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml index 61e389f9fc..8d42b8303f 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -458,6 +458,8 @@ Vlastní RTC Vlastní nastavení času Nastavit vlastní RTC + Zakázat ověřování NCA + Zakáže ověřování integrity archivů obsahu NCA. To může zlepšit rychlost načítání, ale hrozí poškození dat nebo neodhalení neplatných souborů. Je nutné, aby fungovaly hry a aktualizace vyžadující firmware 20+. Generovat @@ -691,7 +693,8 @@ Relativní střed joysticku D-pad slide Haptická odezva - Zobrazit překryv + Zobrazit ovladač + Skrýt ovladač Přepnout vše Upravit překryv Měřítko diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index bff0b0379e..907b114388 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -486,6 +486,8 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die RTC-Datum auswählen RTC-Zeit auswählen Benutzerdefinierte Echtzeituhr + NCA-Verifizierung deaktivieren + Deaktiviert die Integritätsprüfung von NCA-Inhaltsarchiven. Dies kann die Ladegeschwindigkeit verbessern, riskiert jedoch Datenbeschädigung oder dass ungültige Dateien unentdeckt bleiben. Ist notwendig, um Spiele und Updates, die Firmware 20+ benötigen, zum Laufen zu bringen. Generieren @@ -762,6 +764,13 @@ Wirklich fortfahren? Emulation beenden Fertig FPS Zähler + + Steuerung umschalten + Relativer Stick-Zentrum + D-Pad-Scrollen + Haptisches Feedback + Controller anzeigen + Controller ausblenden Alle umschalten Overlay anpassen Größe diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 888d6d1684..12b7183cae 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -506,6 +506,8 @@ RTC personalizado Te permite tener un reloj personalizado en tiempo real diferente del tiempo del propio sistema. Configurar RTC personalizado + Desactivar verificación NCA + Desactiva la verificación de integridad de los archivos de contenido NCA. Esto puede mejorar la velocidad de carga, pero arriesga corrupción de datos o que archivos inválidos pasen desapercibidos. Es necesario para que funcionen juegos y actualizaciones que requieren firmware 20+. Generar @@ -806,7 +808,8 @@ Centro relativo del stick Deslizamiento de la cruceta Toques hápticos - Mostrar overlay + Mostrar controlador + Ocultar controlador Alternar todo Ajustar overlay Escala diff --git a/src/android/app/src/main/res/values-fa/strings.xml b/src/android/app/src/main/res/values-fa/strings.xml index 60b1626aa5..d7ac1b770a 100644 --- a/src/android/app/src/main/res/values-fa/strings.xml +++ b/src/android/app/src/main/res/values-fa/strings.xml @@ -504,6 +504,8 @@ زمان سفارشی به شما امکان می‌دهد یک ساعت سفارشی جدا از زمان فعلی سیستم خود تنظیم کنید. تنظیم زمان سفارشی + غیرفعال کردن تأیید اعتبار NCA + بررسی صحت آرشیوهای محتوای NCA را غیرفعال می‌کند. این ممکن است سرعت بارگذاری را بهبود بخشد اما خطر خرابی داده یا تشخیص داده نشدن فایل‌های نامعتبر را به همراه دارد. برای کار کردن بازی‌ها و به‌روزرسانی‌هایی که به فرمور ۲۰+ نیاز دارند، ضروری است. تولید @@ -805,7 +807,8 @@ مرکز نسبی استیک لغزش دکمه‌های جهتی لرزش لمسی - نشان دادن نمایش روی صفحه + نمایش کنترلر + پنهان کردن کنترلر تغییر همه تنظیم نمایش روی صفحه مقیاس diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index fde02d1aa8..62e67a6fee 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -506,6 +506,8 @@ RTC personnalisé Vous permet de définir une horloge en temps réel personnalisée distincte de l\'heure actuelle de votre système. Définir l\'horloge RTC personnalisée + Désactiver la vérification NCA + Désactive la vérification d\'intégrité des archives de contenu NCA. Cela peut améliorer la vitesse de chargement mais risque une corruption des données ou que des fichiers invalides ne soient pas détectés. Est nécessaire pour faire fonctionner les jeux et mises à jour nécessitant un firmware 20+. Générer @@ -854,7 +856,8 @@ Centre du stick relatif Glissement du D-pad Toucher haptique - Afficher l\'overlay + Afficher la manette + Masquer la manette Tout basculer Ajuster l\'overlay Échelle diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml index 59312086e9..ec5b526ce0 100644 --- a/src/android/app/src/main/res/values-he/strings.xml +++ b/src/android/app/src/main/res/values-he/strings.xml @@ -505,6 +505,8 @@ RTC מותאם אישית מאפשר לך לקבוע שעון זמן אמת נפרד משעון המערכת שלך. קבע RTC מותאם אישית + השבת אימות NCA + משבית את אימות השלמות של ארכיוני התוכן של NCA. זה עשוי לשפר את מהירות הטעינה אך מסתכן בשחיקת נתונים או שמא קבצים לא חוקיים יעברו ללא זיהוי. זה הכרחי כדי לגרום למשחקים ועדכונים הדורשים firmware 20+ לעבוד. יצירה @@ -739,7 +741,8 @@ מרכז ג׳ויסטיק יחסי החלקת D-pad רטט מגע - הצג את שכבת-העל + הצג בקר + הסתר בקר החלף הכל התאם את שכבת-העל קנה מידה diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml index f95e2d3f97..b45692f147 100644 --- a/src/android/app/src/main/res/values-hu/strings.xml +++ b/src/android/app/src/main/res/values-hu/strings.xml @@ -501,6 +501,8 @@ Egyéni RTC Megadhatsz egy valós idejű órát, amely eltér a rendszer által használt órától. Egyéni RTC beállítása + NCA ellenőrzés letiltása + Letiltja az NCA tartalomarchívumok integritás-ellenőrzését. Ez javíthatja a betöltési sebességet, de az adatsérülés vagy az érvénytelen fájlok észrevétlen maradásának kockázatával jár. Elengedhetetlen a 20+ firmware-et igénylő játékok és frissítések működtetéséhez. Generálás @@ -843,7 +845,8 @@ Irányítás átkapcsolása D-pad csúsztatása Érintés haptikája - Átfedés mutatása + Vezérlő megjelenítése + Vezérlő elrejtése Összes átkapcsolása Átfedés testreszabása Skálázás diff --git a/src/android/app/src/main/res/values-id/strings.xml b/src/android/app/src/main/res/values-id/strings.xml index dae77d53af..1817fc654a 100644 --- a/src/android/app/src/main/res/values-id/strings.xml +++ b/src/android/app/src/main/res/values-id/strings.xml @@ -502,6 +502,8 @@ RTC Kustom Memungkinkan Anda untuk mengatur jam waktu nyata kustom yang terpisah dari waktu sistem saat ini Anda. Setel RTC Kustom + Nonaktifkan Verifikasi NCA + Menonaktifkan verifikasi integritas arsip konten NCA. Ini dapat meningkatkan kecepatan pemuatan tetapi berisiko kerusakan data atau file yang tidak valid tidak terdeteksi. Diperlukan untuk membuat game dan pembaruan yang membutuhkan firmware 20+ bekerja. Hasilkan @@ -798,7 +800,8 @@ Pusat stick relatif Geser Dpad Haptik - Tampilkan Hamparan + Tampilkan Kontroler + Sembunyikan Kontroler Alihkan Semua Menyesuaikan Skala diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index dd184e9d9a..ff3706dd40 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml @@ -505,6 +505,8 @@ RTC Personalizzato Ti permette di impostare un orologio in tempo reale personalizzato, completamente separato da quello di sistema. Imposta un orologio in tempo reale personalizzato + Disabilita verifica NCA + Disabilita la verifica dell\'integrità degli archivi di contenuto NCA. Può migliorare la velocità di caricamento ma rischia il danneggiamento dei dati o che file non validi passino inosservati. Necessario per far funzionare giochi e aggiornamenti che richiedono il firmware 20+. Genera @@ -770,7 +772,8 @@ Centro relativo degli Stick DPad A Scorrimento Feedback Aptico - Mostra l\'overlay + Mostra l\'controller + Nascondi l\'controller Attiva/Disattiva tutto Regola l\'overlay Scala diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index 873d433fc0..a6c1ebf8ab 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml @@ -491,6 +491,8 @@ カスタム RTC 現在のシステム時間とは別に、任意のリアルタイムクロックを設定できます。 カスタムRTCを設定 + NCA検証を無効化 + NCAコンテンツアーカイブの整合性検証を無効にします。読み込み速度が向上する可能性がありますが、データ破損や不正なファイルが検出されないリスクがあります。ファームウェア20以上が必要なゲームや更新を動作させるために必要です。 生成 @@ -729,7 +731,8 @@ スティックを固定しない 十字キーをスライド操作 タッチ振動 - ボタンを表示 + コントローラーを表示 + コントローラーを非表示 すべて切替 見た目を調整 大きさ diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index 3f3a4a96c0..01e2c5f4c0 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml @@ -501,6 +501,8 @@ 사용자 지정 RTC 현재 시스템 시간과 별도로 사용자 지정 RTC를 설정할 수 있습니다. 사용자 지정 RTC 설정 + NCA 검증 비활성화 + NCA 콘텐츠 아카이브의 무결성 검증을 비활성화합니다. 로딩 속도를 향상시킬 수 있지만 데이터 손상이나 유효하지 않은 파일이 미검증될 위험이 있습니다. 펌웨어 20+가 필요한 게임 및 업데이트를 실행하려면 필요합니다. 생성 @@ -798,6 +800,7 @@ 십자키 슬라이드 터치 햅틱 컨트롤러 표시 + 컨트롤러 숨기기 모두 선택 컨트롤러 조정 크기 diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index 1e898fca79..90c0dbf05e 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml @@ -482,6 +482,8 @@ Tilpasset Sannhetstidsklokke Gjør det mulig å stille inn en egendefinert sanntidsklokke separat fra den gjeldende systemtiden. Angi tilpasset RTC + Deaktiver NCA-verifisering + Deaktiverer integritetsverifisering av NCA-innholdsarkiv. Dette kan forbedre lastehastigheten, men medfører risiko for datakorrupsjon eller at ugyldige filer ikke oppdages. Er nødvendig for å få spill og oppdateringer som trenger firmware 20+ til å fungere. Generer @@ -720,7 +722,8 @@ Relativt pinnesenter D-pad-skyving Berøringshaptikk - Vis overlegg + Vis kontroller + Skjul kontroller Veksle mellom alle Juster overlegg Skaler diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index 724d7608b6..080064edaf 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -482,6 +482,8 @@ Niestandardowy RTC Ta opcja pozwala na wybranie własnych ustawień czasu używanych w czasie emulacji, innych niż czas systemu Android. Ustaw niestandardowy czas RTC + Wyłącz weryfikację NCA + Wyłącza weryfikację integralności archiwów zawartości NCA. Może to poprawić szybkość ładowania, ale niesie ryzyko uszkodzenia danych lub niezauważenia nieprawidłowych plików. Konieczne, aby działały gry i aktualizacje wymagające firmware\'u 20+. Generuj @@ -718,7 +720,8 @@ Wycentruj gałki Ruchomy D-pad Wibracje haptyczne - Pokaż przyciski + Pokaż kontroler + Ukryj kontroler Włącz wszystkie Dostosuj nakładkę Skala diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index a3fd3fe13a..3dc1f83e6e 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml @@ -506,6 +506,8 @@ Data e hora personalizadas Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo. Definir um relógio em tempo real personalizado + Desativar verificação NCA + Desativa a verificação de integridade de arquivos de conteúdo NCA. Pode melhorar a velocidade de carregamento, mas arrisica corromper dados ou que arquivos inválidos passem despercebidos. É necessário para fazer jogos e atualizações que exigem firmware 20+ funcionarem. Gerar @@ -855,7 +857,8 @@ uma tentativa de mapeamento automático Centro Relativo do Analógico Deslizamento dos Botões Direcionais Vibração ao tocar - Mostrar overlay + Mostrar controle + Ocultar controle Marcar/Desmarcar tudo Ajustar overlay Escala diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 7adce075cf..feb4950fcb 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml @@ -506,6 +506,8 @@ RTC personalizado Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo. Defina um relógio em tempo real personalizado + Desativar verificação NCA + Desativa a verificação de integridade dos arquivos de conteúdo NCA. Pode melhorar a velocidade de carregamento, mas arrisca a corrupção de dados ou que ficheiros inválidos passem despercebidos. É necessário para que jogos e atualizações que necessitam de firmware 20+ funcionem. Gerar @@ -855,7 +857,8 @@ uma tentativa de mapeamento automático Centro Relativo de Analógico Deslizamento dos Botões Direcionais Vibração ao tocar - Mostrar overlay + Mostrar comando + Ocultar comando Marcar/Desmarcar tudo Ajustar overlay Escala diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index 8d02ff7b58..d56c94f10b 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -508,6 +508,8 @@ Пользовательский RTC Позволяет установить пользовательские часы реального времени отдельно от текущего системного времени. Установить пользовательский RTC + Отключить проверку NCA + Отключает проверку целостности архивов содержимого NCA. Может улучшить скорость загрузки, но есть риск повреждения данных или того, что недействительные файлы останутся незамеченными. Необходимо для работы игр и обновлений, требующих прошивку 20+. Создать @@ -856,7 +858,8 @@ Относительный центр стика Слайд крестовиной Обратная связь от нажатий - Показать оверлей + Показать контроллер + Скрыть контроллер Переключить всё Регулировка оверлея Масштаб diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml index 2294033550..5b444f6187 100644 --- a/src/android/app/src/main/res/values-sr/strings.xml +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -457,6 +457,8 @@ Цустом РТЦ Омогућава вам да поставите прилагођени сат у реалном времену одвојено од тренутног времена система. Подесите прилагођени РТЦ + Искључи верификацију НЦА + Искључује верификацију интегритета НЦА архива садржаја. Ово може побољшати брзину учитавања, али ризикује оштећење података или да неважећи фајлови прођу незапажено. Неопходно је да би игре и ажурирања која захтевају firmware 20+ радили. Генериши @@ -812,7 +814,8 @@ Релативни центар за штапић Д-Пад Слиде Додирните ХАптицс - Приказати прекривање + Приказати контролер + Сакрити контролер Пребацивати све Подесити прекривање Скала diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index ebb5493f12..4ae61340dc 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -495,6 +495,8 @@ Свій RTC Дозволяє встановити власний час (Real-time clock, або RTC), відмінний від системного. Встановити RTC + Вимкнути перевірку NCA + Вимкає перевірку цілісності архівів вмісту NCA. Може покращити швидкість завантаження, але ризикує пошкодженням даних або тим, що недійсні файли залишаться непоміченими. Необхідно для роботи ігор та оновлень, які вимагають прошивки 20+. Створити @@ -749,7 +751,8 @@ Відносний центр джойстика Ковзання D-pad Тактильний відгук - Показати накладання + Показати контролер + Сховати контролер Перемкнути все Налаштувати накладання Масштаб diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml index 102c720835..dd64fbca55 100644 --- a/src/android/app/src/main/res/values-vi/strings.xml +++ b/src/android/app/src/main/res/values-vi/strings.xml @@ -482,6 +482,8 @@ RTC tuỳ chỉnh Cho phép bạn thiết lập một đồng hồ thời gian thực tùy chỉnh riêng biệt so với thời gian hệ thống hiện tại. Thiết lập RTC tùy chỉnh + Tắt xác minh NCA + Tắt xác minh tính toàn vẹn của kho lưu trữ nội dung NCA. Có thể cải thiện tốc độ tải nhưng có nguy cơ hỏng dữ liệu hoặc các tệp không hợp lệ không bị phát hiện. Cần thiết để các trò chơi và bản cập nhật yêu cầu firmware 20+ hoạt động. Tạo @@ -723,7 +725,8 @@ Trung tâm nút cần xoay tương đối Trượt D-pad Chạm haptics - Hiện lớp phủ + Hiện bộ điều khiển + Ẩn bộ điều khiển Chuyển đổi tất cả Điều chỉnh lớp phủ Tỉ lệ thu phóng diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index a0dab375d0..a12c00063f 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -500,6 +500,8 @@ 自定义系统时间 此选项允许您设置与目前系统时间相独立的自定义系统时钟。 设置自定义系统时间 +禁用NCA验证 +禁用NCA内容存档的完整性验证。可能会提高加载速度,但存在数据损坏或无效文件未被检测到的风险。对于需要固件20+的游戏和更新是必需的。 生成 @@ -848,7 +850,8 @@ 相对摇杆中心 十字方向键滑动 触觉反馈 - 显示虚拟按键 + 显示控制器 + 隐藏控制器 全部切换 调整虚拟按键 缩放 diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 851483668a..a125553102 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -505,6 +505,8 @@ 自訂 RTC 允許您設定與您的目前系統時間相互獨立的自訂即時時鐘。 設定自訂 RTC + 停用NCA驗證 + 停用NCA內容存檔的完整性驗證。可能會提高載入速度,但存在資料損毀或無效檔案未被偵測到的風險。對於需要韌體20+的遊戲和更新是必需的。 生成 @@ -853,7 +855,8 @@ 相對搖桿中心 方向鍵滑動 觸覺回饋技術 - 顯示覆疊 + 顯示控制器 + 隱藏控制器 全部切換 調整覆疊 縮放 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index f73fc1d9aa..2c7923d5a3 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -474,6 +474,8 @@ Custom RTC Allows you to set a custom real-time clock separate from your current system time. Set custom RTC + Disable NCA Verification + Disables integrity verification of NCA content archives. This may improve loading speed but risks data corruption or invalid files going undetected. Some games that require firmware versions 20+ may need this as well. Generate @@ -782,6 +784,9 @@ Game Requires Firmware dump and install firmware, or press "OK" to launch anyways.]]> + NCA Verification Disabled + This is required to run new games and updates, but may cause instability or crashes if NCA files are corrupt, modified, or tampered with. If unsure, re-enable verification in Advanced Settings -> System, and use firmware versions of 19.0.1 or below. + Searching for game... Game not found for Title ID: %1$s @@ -835,7 +840,8 @@ Relative stick center D-pad slide Touch haptics - Show overlay + Show controller + Hide controller Toggle all Adjust overlay Scale diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index e040ec756d..389b1044e9 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2018 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later @@ -229,9 +232,10 @@ endif() target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS}) target_link_libraries(audio_core PUBLIC common core opus) -if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) - target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) -endif() +# what? +# if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) +# target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) +# endif() if (ENABLE_CUBEB) target_sources(audio_core PRIVATE @@ -240,7 +244,7 @@ if (ENABLE_CUBEB) ) target_link_libraries(audio_core PRIVATE cubeb) - target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1) + target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1) endif() if (ENABLE_SDL2) diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h index eef2a844ba..39d50746b8 100644 --- a/src/audio_core/common/feature_support.h +++ b/src/audio_core/common/feature_support.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,7 +16,7 @@ #include "common/polyfill_ranges.h" namespace AudioCore { -constexpr u32 CurrentRevision = 13; +constexpr u32 CurrentRevision = 16; enum class SupportTags { CommandProcessingTimeEstimatorVersion4, @@ -54,6 +57,7 @@ constexpr u32 GetRevisionNum(u32 user_revision) { user_revision -= Common::MakeMagic('R', 'E', 'V', '0'); user_revision >>= 24; } + return user_revision; }; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6173e29f45..9b898837bc 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -162,10 +162,6 @@ add_library( zstd_compression.h ) -if(YUZU_ENABLE_PORTABLE) - add_compile_definitions(YUZU_ENABLE_PORTABLE) -endif() - if(WIN32) target_sources(common PRIVATE windows/timer_resolution.cpp windows/timer_resolution.h) @@ -262,23 +258,18 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") endif() if (BOOST_NO_HEADERS) - target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool) + target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool) else() - target_link_libraries(common PUBLIC Boost::headers) + target_link_libraries(common PUBLIC Boost::headers) endif() if (lz4_ADDED) - target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) + target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) endif() target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads) target_link_libraries(common PRIVATE lz4::lz4 LLVM::Demangle zstd::zstd) -if (TARGET unordered_dense::unordered_dense) - # weird quirk of system installs - target_link_libraries(common PUBLIC unordered_dense::unordered_dense) -endif() - if(ANDROID) # For ASharedMemory_create target_link_libraries(common PRIVATE android) diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index 40891380c9..5cdf9be39d 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -12,7 +12,6 @@ #define PORTABLE_DIR "user" // Sub-directories contained within a yuzu data directory - #define AMIIBO_DIR "amiibo" #define CACHE_DIR "cache" #define CONFIG_DIR "config" @@ -28,11 +27,12 @@ #define SHADER_DIR "shader" #define TAS_DIR "tas" #define ICONS_DIR "icons" + +// Compatibility with other emulators #define CITRON_DIR "citron" #define SUDACHI_DIR "sudachi" #define YUZU_DIR "yuzu" #define SUYU_DIR "suyu" // yuzu-specific files - #define LOG_FILE "eden_log.txt" diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index fa1403225e..a2f5cb92ff 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -101,61 +101,53 @@ public: legacy_paths.insert_or_assign(legacy_path, new_path); } + /// In non-android devices, the current directory will first search for "user" + /// if such directory (and it must be a directory) is found, that takes priority + /// over the global configuration directory (in other words, portable directories + /// take priority over the global ones, always) + /// On Android, the behaviour is to look for the current directory only. void Reinitialize(fs::path eden_path = {}) { fs::path eden_path_cache; fs::path eden_path_config; - #ifdef _WIN32 -#ifdef YUZU_ENABLE_PORTABLE + // User directory takes priority over global %AppData% directory eden_path = GetExeDirectory() / PORTABLE_DIR; -#endif - if (!IsDir(eden_path)) { + if (!Exists(eden_path) || !IsDir(eden_path)) { eden_path = GetAppDataRoamingDirectory() / EDEN_DIR; } - eden_path_cache = eden_path / CACHE_DIR; eden_path_config = eden_path / CONFIG_DIR; - #define LEGACY_PATH(titleName, upperName) GenerateLegacyPath(LegacyPath::titleName##Dir, GetAppDataRoamingDirectory() / upperName##_DIR); \ GenerateLegacyPath(LegacyPath::titleName##ConfigDir, GetAppDataRoamingDirectory() / upperName##_DIR / CONFIG_DIR); \ GenerateLegacyPath(LegacyPath::titleName##CacheDir, GetAppDataRoamingDirectory() / upperName##_DIR / CACHE_DIR); - LEGACY_PATH(Citron, CITRON) LEGACY_PATH(Sudachi, SUDACHI) LEGACY_PATH(Yuzu, YUZU) LEGACY_PATH(Suyu, SUYU) #undef LEGACY_PATH - #elif ANDROID ASSERT(!eden_path.empty()); eden_path_cache = eden_path / CACHE_DIR; eden_path_config = eden_path / CONFIG_DIR; #else -#ifdef YUZU_ENABLE_PORTABLE eden_path = GetCurrentDir() / PORTABLE_DIR; -#endif - if (Exists(eden_path) && IsDir(eden_path)) { - eden_path_cache = eden_path / CACHE_DIR; - eden_path_config = eden_path / CONFIG_DIR; - } else { + if (!Exists(eden_path) || !IsDir(eden_path)) { eden_path = GetDataDirectory("XDG_DATA_HOME") / EDEN_DIR; eden_path_cache = GetDataDirectory("XDG_CACHE_HOME") / EDEN_DIR; eden_path_config = GetDataDirectory("XDG_CONFIG_HOME") / EDEN_DIR; + } else { + eden_path_cache = eden_path / CACHE_DIR; + eden_path_config = eden_path / CONFIG_DIR; } - #define LEGACY_PATH(titleName, upperName) GenerateLegacyPath(LegacyPath::titleName##Dir, GetDataDirectory("XDG_DATA_HOME") / upperName##_DIR); \ GenerateLegacyPath(LegacyPath::titleName##ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / upperName##_DIR); \ GenerateLegacyPath(LegacyPath::titleName##CacheDir, GetDataDirectory("XDG_CACHE_HOME") / upperName##_DIR); - LEGACY_PATH(Citron, CITRON) LEGACY_PATH(Sudachi, SUDACHI) LEGACY_PATH(Yuzu, YUZU) LEGACY_PATH(Suyu, SUYU) - #undef LEGACY_PATH - #endif - GenerateEdenPath(EdenPath::EdenDir, eden_path); GenerateEdenPath(EdenPath::AmiiboDir, eden_path / AMIIBO_DIR); GenerateEdenPath(EdenPath::CacheDir, eden_path_cache); diff --git a/src/common/heap_tracker.cpp b/src/common/heap_tracker.cpp index c147c279bd..c875683f0f 100644 --- a/src/common/heap_tracker.cpp +++ b/src/common/heap_tracker.cpp @@ -1,5 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -35,60 +33,68 @@ HeapTracker::~HeapTracker() = default; void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm, bool is_separate_heap) { - bool rebuild_required = false; // When mapping other memory, map pages immediately. if (!is_separate_heap) { m_buffer.Map(virtual_offset, host_offset, length, perm, false); return; } + { - // We are mapping part of a separate heap and insert into mappings. + // We are mapping part of a separate heap. std::scoped_lock lk{m_lock}; - m_map_count++; - const auto it = m_mappings.insert_or_assign(virtual_offset, SeparateHeapMap{ + + auto* const map = new SeparateHeapMap{ + .vaddr = virtual_offset, .paddr = host_offset, .size = length, .tick = m_tick++, .perm = perm, .is_resident = false, - }); - // Update tick before possible rebuild. - it.first->second.tick = m_tick++; - // Check if we need to rebuild. - if (m_resident_map_count >= m_max_resident_map_count) - rebuild_required = true; - // Map the area. - m_buffer.Map(it.first->first, it.first->second.paddr, it.first->second.size, it.first->second.perm, false); - // This map is now resident. - it.first->second.is_resident = true; - m_resident_map_count++; - m_resident_mappings.insert(*it.first); + }; + + // Insert into mappings. + m_map_count++; + m_mappings.insert(*map); } - // A rebuild was required, so perform it now. - if (rebuild_required) - this->RebuildSeparateHeapAddressSpace(); + + // Finally, map. + this->DeferredMapSeparateHeap(virtual_offset); } void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_heap) { // If this is a separate heap... if (is_separate_heap) { std::scoped_lock lk{m_lock}; + + const SeparateHeapMap key{ + .vaddr = virtual_offset, + }; + // Split at the boundaries of the region we are removing. this->SplitHeapMapLocked(virtual_offset); this->SplitHeapMapLocked(virtual_offset + size); + // Erase all mappings in range. - auto it = m_mappings.find(virtual_offset); - while (it != m_mappings.end() && it->first < virtual_offset + size) { + auto it = m_mappings.find(key); + while (it != m_mappings.end() && it->vaddr < virtual_offset + size) { + // Get underlying item. + auto* const item = std::addressof(*it); + // If resident, erase from resident map. - if (it->second.is_resident) { + if (item->is_resident) { ASSERT(--m_resident_map_count >= 0); - m_resident_mappings.erase(m_resident_mappings.find(it->first)); + m_resident_mappings.erase(m_resident_mappings.iterator_to(*item)); } + // Erase from map. ASSERT(--m_map_count >= 0); it = m_mappings.erase(it); + + // Free the item. + delete item; } } + // Unmap pages. m_buffer.Unmap(virtual_offset, size, false); } @@ -110,51 +116,110 @@ void HeapTracker::Protect(size_t virtual_offset, size_t size, MemoryPermission p { std::scoped_lock lk2{m_lock}; + + const SeparateHeapMap key{ + .vaddr = next, + }; + // Try to get the next mapping corresponding to this address. - const auto it = m_mappings.find(next); + const auto it = m_mappings.nfind(key); + if (it == m_mappings.end()) { // There are no separate heap mappings remaining. next = end; should_protect = true; - } else if (it->first == cur) { + } else if (it->vaddr == cur) { // We are in range. // Update permission bits. - it->second.perm = perm; + it->perm = perm; // Determine next address and whether we should protect. - next = cur + it->second.size; - should_protect = it->second.is_resident; + next = cur + it->size; + should_protect = it->is_resident; } else /* if (it->vaddr > cur) */ { // We weren't in range, but there is a block coming up that will be. - next = it->first; + next = it->vaddr; should_protect = true; } } // Clamp to end. next = std::min(next, end); + // Reprotect, if we need to. - if (should_protect) + if (should_protect) { m_buffer.Protect(cur, next - cur, perm); + } + // Advance. cur = next; } } +bool HeapTracker::DeferredMapSeparateHeap(u8* fault_address) { + if (m_buffer.IsInVirtualRange(fault_address)) { + return this->DeferredMapSeparateHeap(fault_address - m_buffer.VirtualBasePointer()); + } + + return false; +} + +bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) { + bool rebuild_required = false; + + { + std::scoped_lock lk{m_lock}; + + // Check to ensure this was a non-resident separate heap mapping. + const auto it = this->GetNearestHeapMapLocked(virtual_offset); + if (it == m_mappings.end() || it->is_resident) { + return false; + } + + // Update tick before possible rebuild. + it->tick = m_tick++; + + // Check if we need to rebuild. + if (m_resident_map_count > m_max_resident_map_count) { + rebuild_required = true; + } + + // Map the area. + m_buffer.Map(it->vaddr, it->paddr, it->size, it->perm, false); + + // This map is now resident. + it->is_resident = true; + m_resident_map_count++; + m_resident_mappings.insert(*it); + } + + if (rebuild_required) { + // A rebuild was required, so perform it now. + this->RebuildSeparateHeapAddressSpace(); + } + + return true; +} + void HeapTracker::RebuildSeparateHeapAddressSpace() { std::scoped_lock lk{m_rebuild_lock, m_lock}; + ASSERT(!m_resident_mappings.empty()); + // Dump half of the mappings. + // // Despite being worse in theory, this has proven to be better in practice than more // regularly dumping a smaller amount, because it significantly reduces average case // lock contention. - std::size_t const desired_count = std::min(m_resident_map_count, m_max_resident_map_count) / 2; - std::size_t const evict_count = m_resident_map_count - desired_count; + const size_t desired_count = std::min(m_resident_map_count, m_max_resident_map_count) / 2; + const size_t evict_count = m_resident_map_count - desired_count; auto it = m_resident_mappings.begin(); - for (std::size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) { + + for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) { // Unmark and unmap. - it->second.is_resident = false; - m_buffer.Unmap(it->first, it->second.size, false); + it->is_resident = false; + m_buffer.Unmap(it->vaddr, it->size, false); + // Advance. ASSERT(--m_resident_map_count >= 0); it = m_resident_mappings.erase(it); @@ -163,32 +228,53 @@ void HeapTracker::RebuildSeparateHeapAddressSpace() { void HeapTracker::SplitHeapMap(VAddr offset, size_t size) { std::scoped_lock lk{m_lock}; + this->SplitHeapMapLocked(offset); this->SplitHeapMapLocked(offset + size); } void HeapTracker::SplitHeapMapLocked(VAddr offset) { - auto it = this->GetNearestHeapMapLocked(offset); - if (it != m_mappings.end() && it->first != offset) { - // Adjust left iterator - auto const orig_size = it->second.size; - auto const left_size = offset - it->first; - it->second.size = left_size; - // Insert the new right map. - auto const right = SeparateHeapMap{ - .paddr = it->second.paddr + left_size, - .size = orig_size - left_size, - .tick = it->second.tick, - .perm = it->second.perm, - .is_resident = it->second.is_resident, - }; - m_map_count++; - auto rit = m_mappings.insert_or_assign(it->first + left_size, right); - if (rit.first->second.is_resident) { - m_resident_map_count++; - m_resident_mappings.insert(*rit.first); - } + const auto it = this->GetNearestHeapMapLocked(offset); + if (it == m_mappings.end() || it->vaddr == offset) { + // Not contained or no split required. + return; + } + + // Cache the original values. + auto* const left = std::addressof(*it); + const size_t orig_size = left->size; + + // Adjust the left map. + const size_t left_size = offset - left->vaddr; + left->size = left_size; + + // Create the new right map. + auto* const right = new SeparateHeapMap{ + .vaddr = left->vaddr + left_size, + .paddr = left->paddr + left_size, + .size = orig_size - left_size, + .tick = left->tick, + .perm = left->perm, + .is_resident = left->is_resident, + }; + + // Insert the new right map. + m_map_count++; + m_mappings.insert(*right); + + // If resident, also insert into resident map. + if (right->is_resident) { + m_resident_map_count++; + m_resident_mappings.insert(*right); } } +HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) { + const SeparateHeapMap key{ + .vaddr = offset, + }; + + return m_mappings.find(key); +} + } // namespace Common diff --git a/src/common/heap_tracker.h b/src/common/heap_tracker.h index 14b5401c18..ee5b0bf43a 100644 --- a/src/common/heap_tracker.h +++ b/src/common/heap_tracker.h @@ -1,55 +1,93 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include +#include #include -#include + #include "common/host_memory.h" +#include "common/intrusive_red_black_tree.h" namespace Common { struct SeparateHeapMap { - PAddr paddr{}; //8 - std::size_t size{}; //8 (16) - std::size_t tick{}; //8 (24) - // 4 bits needed, sync with host_memory.h if needed - MemoryPermission perm : 4 = MemoryPermission::Read; - bool is_resident : 1 = false; + Common::IntrusiveRedBlackTreeNode addr_node{}; + Common::IntrusiveRedBlackTreeNode tick_node{}; + VAddr vaddr{}; + PAddr paddr{}; + size_t size{}; + size_t tick{}; + MemoryPermission perm{}; + bool is_resident{}; +}; + +struct SeparateHeapMapAddrComparator { + static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) { + if (lhs.vaddr < rhs.vaddr) { + return -1; + } else if (lhs.vaddr <= (rhs.vaddr + rhs.size - 1)) { + return 0; + } else { + return 1; + } + } +}; + +struct SeparateHeapMapTickComparator { + static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) { + if (lhs.tick < rhs.tick) { + return -1; + } else if (lhs.tick > rhs.tick) { + return 1; + } else { + return SeparateHeapMapAddrComparator::Compare(lhs, rhs); + } + } }; -static_assert(sizeof(SeparateHeapMap) == 32); //half a cache line! good for coherency class HeapTracker { public: explicit HeapTracker(Common::HostMemory& buffer); ~HeapTracker(); - void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm, bool is_separate_heap); + + void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm, + bool is_separate_heap); void Unmap(size_t virtual_offset, size_t size, bool is_separate_heap); void Protect(size_t virtual_offset, size_t length, MemoryPermission perm); - inline u8* VirtualBasePointer() noexcept { + u8* VirtualBasePointer() { return m_buffer.VirtualBasePointer(); } + + bool DeferredMapSeparateHeap(u8* fault_address); + bool DeferredMapSeparateHeap(size_t virtual_offset); + private: - // TODO: You may want to "fake-map" the first 2GB of 64-bit address space - // and dedicate it entirely to a recursive PTE mapping :) - // However Ankerl is way better than using an RB tree, in all senses - using AddrTree = ankerl::unordered_dense::map; - AddrTree m_mappings; - using TicksTree = ankerl::unordered_dense::map; - TicksTree m_resident_mappings; + using AddrTreeTraits = + Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::addr_node>; + using AddrTree = AddrTreeTraits::TreeType; + + using TickTreeTraits = + Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::tick_node>; + using TickTree = TickTreeTraits::TreeType; + + AddrTree m_mappings{}; + TickTree m_resident_mappings{}; + private: void SplitHeapMap(VAddr offset, size_t size); void SplitHeapMapLocked(VAddr offset); + + AddrTree::iterator GetNearestHeapMapLocked(VAddr offset); + void RebuildSeparateHeapAddressSpace(); - inline HeapTracker::AddrTree::iterator GetNearestHeapMapLocked(VAddr offset) noexcept { - return m_mappings.find(offset); - } + private: Common::HostMemory& m_buffer; const s64 m_max_resident_map_count; + std::shared_mutex m_rebuild_lock{}; std::mutex m_lock{}; s64 m_map_count{}; diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index edb64de8ec..15a198e216 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -12,7 +12,7 @@ #include #include "common/dynamic_library.h" -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) // ^^^ Windows ^^^ vvv Linux vvv +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__APPLE__) // ^^^ Windows ^^^ vvv POSIX vvv #ifndef _GNU_SOURCE #define _GNU_SOURCE @@ -20,10 +20,18 @@ #include #include #include -#include #include #include "common/scope_exit.h" +#if defined(__linux__) +#include +#elif defined(__APPLE__) +#include +#include +#include +#include +#endif + // FreeBSD #ifndef MAP_NORESERVE #define MAP_NORESERVE 0 @@ -32,8 +40,12 @@ #ifndef MAP_ALIGNED_SUPER #define MAP_ALIGNED_SUPER 0 #endif +// macOS +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif -#endif // ^^^ Linux ^^^ +#endif // ^^^ POSIX ^^^ #include #include @@ -372,7 +384,7 @@ private: std::unordered_map placeholder_host_pointers; ///< Placeholder backing offset }; -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) // ^^^ Windows ^^^ vvv Linux vvv +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__APPLE__) // ^^^ Windows ^^^ vvv POSIX vvv #ifdef ARCHITECTURE_arm64 @@ -417,14 +429,11 @@ static void* ChooseVirtualBase(size_t virtual_size) { #else static void* ChooseVirtualBase(size_t virtual_size) { -#if defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__) void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0); - - if (virtual_base != MAP_FAILED) { + if (virtual_base != MAP_FAILED) return virtual_base; - } #endif - return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); } @@ -492,6 +501,13 @@ public: #elif defined(__FreeBSD__) && __FreeBSD__ < 13 // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30 fd = shm_open(SHM_ANON, O_RDWR, 0600); +#elif defined(__APPLE__) + // macOS doesn't have memfd_create, use anonymous temporary file + char template_path[] = "/tmp/eden_mem_XXXXXX"; + fd = mkstemp(template_path); + if (fd >= 0) { + unlink(template_path); + } #else fd = memfd_create("HostMemory", 0); #endif @@ -648,7 +664,7 @@ private: FreeRegionManager free_manager{}; }; -#else // ^^^ Linux ^^^ vvv Generic vvv +#else // ^^^ POSIX ^^^ vvv Generic vvv class HostMemory::Impl { public: diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 19140bce0d..d4f16f4853 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -163,8 +163,9 @@ bool IsFastmemEnabled() { } #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun__) return false; -#endif +#else return true; +#endif } static bool is_nce_enabled = false; diff --git a/src/common/settings.h b/src/common/settings.h index 64545d10ff..047dfc800a 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -217,7 +217,8 @@ struct Values { true, true, &use_speed_limit}; - SwitchableSetting sync_core_speed{linkage, false, "sync_core_speed", Category::Core, Specialization::Default}; + SwitchableSetting sync_core_speed{linkage, false, "sync_core_speed", Category::Core, + Specialization::Default}; // Memory #ifdef HAS_NCE @@ -551,6 +552,8 @@ struct Values { 3, #elif defined (ANDROID) 0, +#elif defined (__APPLE__) + 0, #else 2, #endif @@ -622,7 +625,11 @@ struct Values { linkage, 0, "rng_seed", Category::System, Specialization::Hex, true, true, &rng_seed_enabled}; Setting device_name{ - linkage, "Eden", "device_name", Category::System, Specialization::Default, true, true}; + linkage, "Eden", "device_name", Category::System, Specialization::Default, true, true}; + SwitchableSetting disable_nca_verification{linkage, true, "disable_nca_verification", + Category::System, Specialization::Default}; + Setting hide_nca_verification_popup{ + linkage, false, "hide_nca_verification_popup", Category::System, Specialization::Default}; Setting current_user{linkage, 0, "current_user", Category::System}; diff --git a/src/common/string_util.h b/src/common/string_util.h index 8ed87cdadc..4358541b14 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index eab506f194..1979d427b5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2018 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later @@ -88,6 +91,8 @@ add_library(core STATIC file_sys/fssystem/fssystem_crypto_configuration.h file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h + file_sys/fssystem/fssystem_hierarchical_sha3_storage.cpp + file_sys/fssystem/fssystem_hierarchical_sha3_storage.h file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp file_sys/fssystem/fssystem_hierarchical_sha256_storage.h file_sys/fssystem/fssystem_indirect_storage.cpp @@ -102,6 +107,7 @@ add_library(core STATIC file_sys/fssystem/fssystem_nca_header.cpp file_sys/fssystem/fssystem_nca_header.h file_sys/fssystem/fssystem_nca_reader.cpp + file_sys/fssystem/fssystem_passthrough_storage.h file_sys/fssystem/fssystem_pooled_buffer.cpp file_sys/fssystem/fssystem_pooled_buffer.h file_sys/fssystem/fssystem_sparse_storage.cpp @@ -1152,7 +1158,7 @@ add_library(core STATIC if (ENABLE_WIFI_SCAN) # find_package(libiw REQUIRED) - target_compile_definitions(core PRIVATE -DENABLE_WIFI_SCAN) + target_compile_definitions(core PRIVATE ENABLE_WIFI_SCAN) target_link_libraries(core PRIVATE iw) endif() @@ -1193,13 +1199,13 @@ else() target_link_libraries(core PUBLIC Boost::headers) endif() -target_link_libraries(core PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API) +target_link_libraries(core PRIVATE fmt::fmt nlohmann_json::nlohmann_json RenderDoc::API mbedtls) if (MINGW) target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) endif() if (ENABLE_WEB_SERVICE) - target_compile_definitions(core PUBLIC -DENABLE_WEB_SERVICE) + target_compile_definitions(core PUBLIC ENABLE_WEB_SERVICE) target_link_libraries(core PUBLIC web_service) endif() diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 9d26db51f7..e6e9fc45be 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -3,9 +3,47 @@ #ifdef __linux__ -//#include "common/signal_chain.h" +#include "common/signal_chain.h" + #include "core/arm/dynarmic/arm_dynarmic.h" -//#include "core/hle/kernel/k_process.h" -//#include "core/memory.h" +#include "core/hle/kernel/k_process.h" +#include "core/memory.h" + +namespace Core { + +namespace { + +thread_local Core::Memory::Memory* g_current_memory{}; +std::once_flag g_registered{}; +struct sigaction g_old_segv {}; + +void HandleSigSegv(int sig, siginfo_t* info, void* ctx) { + if (g_current_memory && g_current_memory->InvalidateSeparateHeap(info->si_addr)) { + return; + } + + return g_old_segv.sa_sigaction(sig, info, ctx); +} + +} // namespace + +ScopedJitExecution::ScopedJitExecution(Kernel::KProcess* process) { + g_current_memory = std::addressof(process->GetMemory()); +} + +ScopedJitExecution::~ScopedJitExecution() { + g_current_memory = nullptr; +} + +void ScopedJitExecution::RegisterHandler() { + std::call_once(g_registered, [] { + struct sigaction sa {}; + sa.sa_sigaction = &HandleSigSegv; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + Common::SigAction(SIGSEGV, std::addressof(sa), std::addressof(g_old_segv)); + }); +} + +} // namespace Core #endif diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index eef7c31160..53dd188151 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -26,4 +26,24 @@ constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) { return static_cast(hr); } +#ifdef __linux__ + +class ScopedJitExecution { +public: + explicit ScopedJitExecution(Kernel::KProcess* process); + ~ScopedJitExecution(); + static void RegisterHandler(); +}; + +#else + +class ScopedJitExecution { +public: + explicit ScopedJitExecution(Kernel::KProcess* process) {} + ~ScopedJitExecution() {} + static void RegisterHandler() {} +}; + +#endif + } // namespace Core diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 7123497682..2c2c54a1ad 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -343,11 +343,15 @@ bool ArmDynarmic32::IsInThumbMode() const { } HaltReason ArmDynarmic32::RunThread(Kernel::KThread* thread) { + ScopedJitExecution sj(thread->GetOwnerProcess()); + m_jit->ClearExclusiveState(); return TranslateHaltReason(m_jit->Run()); } HaltReason ArmDynarmic32::StepThread(Kernel::KThread* thread) { + ScopedJitExecution sj(thread->GetOwnerProcess()); + m_jit->ClearExclusiveState(); return TranslateHaltReason(m_jit->Step()); } @@ -389,6 +393,7 @@ ArmDynarmic32::ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProc m_cp15(std::make_shared(*this)), m_core_index{core_index} { auto& page_table_impl = process->GetPageTable().GetBasePageTable().GetImpl(); m_jit = MakeJit(&page_table_impl); + ScopedJitExecution::RegisterHandler(); } ArmDynarmic32::~ArmDynarmic32() = default; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 2745aeb862..438b7b691c 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -374,11 +374,15 @@ std::shared_ptr ArmDynarmic64::MakeJit(Common::PageTable* pa } HaltReason ArmDynarmic64::RunThread(Kernel::KThread* thread) { + ScopedJitExecution sj(thread->GetOwnerProcess()); + m_jit->ClearExclusiveState(); return TranslateHaltReason(m_jit->Run()); } HaltReason ArmDynarmic64::StepThread(Kernel::KThread* thread) { + ScopedJitExecution sj(thread->GetOwnerProcess()); + m_jit->ClearExclusiveState(); return TranslateHaltReason(m_jit->Step()); } @@ -418,6 +422,7 @@ ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProc auto& page_table = process->GetPageTable().GetBasePageTable(); auto& page_table_impl = page_table.GetImpl(); m_jit = MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth()); + ScopedJitExecution::RegisterHandler(); } ArmDynarmic64::~ArmDynarmic64() = default; diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 74b1ca04b1..04b75d5e8f 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -31,6 +34,10 @@ #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" +#ifndef MBEDTLS_CMAC_C +#error mbedtls was compiled without CMAC support. Check your USE flags (Gentoo) or contact your package maintainer. +#endif + namespace Core::Crypto { namespace { diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index 7fe22fdce2..460e0d19b4 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp @@ -7,7 +7,7 @@ #include #include -#if BOOST_VERSION > 108300 && (!defined(_WINDOWS) && !defined(ANDROID)) || defined(YUZU_BOOST_v1) +#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(ANDROID)) || defined(YUZU_BOOST_v1) #define USE_BOOST_v1 #endif diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp index af8541009e..615a624f4f 100644 --- a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/settings.h" #include "core/file_sys/errors.h" #include "core/file_sys/fssystem/fssystem_bucket_tree.h" #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" @@ -233,7 +237,10 @@ Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storag void BucketTree::Initialize(size_t node_size, s64 end_offset) { ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); ASSERT(Common::IsPowerOfTwo(node_size)); - ASSERT(end_offset > 0); + + if (!Settings::values.disable_nca_verification.GetValue()) { + ASSERT(end_offset > 0); + } ASSERT(!this->IsInitialized()); m_node_size = node_size; diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp index a68fd973c9..e8669a4a7d 100644 --- a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp @@ -5,23 +5,10 @@ #include "common/scope_exit.h" #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" +#include + namespace FileSys { -namespace { - -s32 Log2(s32 value) { - ASSERT(value > 0); - ASSERT(Common::IsPowerOfTwo(value)); - - s32 log = 0; - while ((value >>= 1) > 0) { - ++log; - } - return log; -} - -} // namespace - Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf, size_t hash_buf_size) { // Validate preconditions. @@ -31,7 +18,8 @@ Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 lay // Set size tracking members. m_hash_target_block_size = static_cast(htbs); - m_log_size_ratio = Log2(m_hash_target_block_size / HashSize); + m_log_size_ratio = + static_cast(std::log2(static_cast(m_hash_target_block_size) / HashSize)); // Get the base storage size. m_base_storage_size = base_storages[2]->GetSize(); diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha3_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha3_storage.cpp new file mode 100644 index 0000000000..d58f2ee9be --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha3_storage.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/alignment.h" +#include "common/scope_exit.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_sha3_storage.h" + +#include + +namespace FileSys { + +Result HierarchicalSha3Storage::Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, + void* hash_buf, size_t hash_buf_size) { + ASSERT(layer_count == LayerCount); + ASSERT(Common::IsPowerOfTwo(htbs)); + ASSERT(hash_buf != nullptr); + + m_hash_target_block_size = static_cast(htbs); + m_log_size_ratio = + static_cast(std::log2(static_cast(m_hash_target_block_size) / HashSize)); + + m_base_storage_size = base_storages[2]->GetSize(); + { + auto size_guard = SCOPE_GUARD { + m_base_storage_size = 0; + }; + R_UNLESS(m_base_storage_size <= static_cast(HashSize) + << m_log_size_ratio << m_log_size_ratio, + ResultHierarchicalSha256BaseStorageTooLarge); + size_guard.Cancel(); + } + + m_base_storage = base_storages[2]; + m_hash_buffer = static_cast(hash_buf); + m_hash_buffer_size = hash_buf_size; + + std::array master_hash{}; + base_storages[0]->ReadObject(std::addressof(master_hash)); + + s64 hash_storage_size = base_storages[1]->GetSize(); + ASSERT(Common::IsAligned(hash_storage_size, HashSize)); + ASSERT(hash_storage_size <= m_hash_target_block_size); + ASSERT(hash_storage_size <= static_cast(m_hash_buffer_size)); + + base_storages[1]->Read(reinterpret_cast(m_hash_buffer), + static_cast(hash_storage_size), 0); + R_SUCCEED(); +} + +size_t HierarchicalSha3Storage::Read(u8* buffer, size_t size, size_t offset) const { + if (size == 0) + return size; + ASSERT(buffer != nullptr); + return m_base_storage->Read(buffer, size, offset); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha3_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha3_storage.h new file mode 100644 index 0000000000..2db7bb28e1 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha3_storage.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +class HierarchicalSha3Storage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(HierarchicalSha3Storage); + YUZU_NON_MOVEABLE(HierarchicalSha3Storage); + +public: + static constexpr s32 LayerCount = 3; + static constexpr size_t HashSize = 256 / 8; // SHA3-256 + +public: + HierarchicalSha3Storage() : m_mutex() {} + + Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf, + size_t hash_buf_size); + + virtual size_t GetSize() const override { + return m_base_storage->GetSize(); + } + + virtual size_t Read(u8* buffer, size_t length, size_t offset) const override; + +private: + VirtualFile m_base_storage; + s64 m_base_storage_size{}; + char* m_hash_buffer{}; + size_t m_hash_buffer_size{}; + s32 m_hash_target_block_size{}; + s32 m_log_size_ratio{}; + std::mutex m_mutex; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp index ab5a7984e3..1bc7039318 100644 --- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/settings.h" #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" @@ -10,10 +14,12 @@ #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" #include "core/file_sys/fssystem/fssystem_indirect_storage.h" #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" +#include "core/file_sys/fssystem/fssystem_passthrough_storage.h" #include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" #include "core/file_sys/fssystem/fssystem_sparse_storage.h" #include "core/file_sys/fssystem/fssystem_switch_storage.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_sha3_storage.h" #include "core/file_sys/vfs/vfs_offset.h" #include "core/file_sys/vfs/vfs_vector.h" @@ -299,18 +305,24 @@ Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out, // Process hash/integrity layer. switch (header_reader->GetHashType()) { case NcaFsHeader::HashType::HierarchicalSha256Hash: - R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), - header_reader->GetHashData().hierarchical_sha256_data)); + R_TRY(CreateSha256Storage(&storage, std::move(storage), + header_reader->GetHashData().hierarchical_sha256_data)); break; case NcaFsHeader::HashType::HierarchicalIntegrityHash: - R_TRY(this->CreateIntegrityVerificationStorage( - std::addressof(storage), std::move(storage), - header_reader->GetHashData().integrity_meta_info)); + R_TRY(CreateIntegrityVerificationStorage(&storage, std::move(storage), + header_reader->GetHashData().integrity_meta_info)); + break; + case NcaFsHeader::HashType::HierarchicalSha3256Hash: + R_TRY(CreateSha3Storage(&storage, std::move(storage), + header_reader->GetHashData().hierarchical_sha256_data)); break; default: + LOG_ERROR(Loader, "Unhandled Fs HashType enum={}", + static_cast(header_reader->GetHashType())); R_THROW(ResultInvalidNcaFsHeaderHashType); } + // Process compression layer. if (header_reader->ExistsCompressionLayer()) { R_TRY(this->CreateCompressedStorage( @@ -679,6 +691,7 @@ Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification( // Create the verification storage. VirtualFile integrity_storage; + Result rc = this->CreateIntegrityVerificationStorageForMeta( std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), meta_offset, meta_data_hash_data_info); @@ -734,8 +747,26 @@ Result NcaFileSystemDriver::CreateSparseStorageWithVerification( NcaHeader::CtrBlockSize))); // Check the meta data hash type. - R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, - ResultRomNcaInvalidSparseMetaDataHashType); + if (meta_data_hash_type != NcaFsHeader::MetaDataHashType::HierarchicalIntegrity) { + LOG_ERROR(Loader, "Sparse meta hash type {} not supported for verification; mounting sparse data WITHOUT verification (temporary).", static_cast(meta_data_hash_type)); + + R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), + sparse_info.physical_offset, + sparse_info.GetPhysicalSize())); + + // Create sparse core directly (no meta verification) + std::shared_ptr sparse_storage_fallback; + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage_fallback), + body_substorage, sparse_info.GetPhysicalSize(), + /*meta_storage*/ body_substorage, // dummy; not used + sparse_info, false)); + + if (out_sparse_storage) + *out_sparse_storage = sparse_storage_fallback; + *out_fs_data_offset = fs_offset; + *out = std::move(sparse_storage_fallback); + R_SUCCEED(); + } // Create the meta storage. VirtualFile meta_storage; @@ -1093,6 +1124,56 @@ Result NcaFileSystemDriver::CreatePatchMetaStorage( R_SUCCEED(); } +Result NcaFileSystemDriver::CreateSha3Storage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + using VerificationStorage = HierarchicalSha3Storage; + + R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size), + ResultInvalidHierarchicalSha256BlockSize); + R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, + ResultInvalidHierarchicalSha256LayerCount); + + const auto& hash_region = hash_data.hash_layer_region[0]; + const auto& data_region = hash_data.hash_layer_region[1]; + + constexpr s32 CacheBlockCount = 2; + const auto hash_buffer_size = static_cast(hash_region.size); + const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; + const auto total_buffer_size = hash_buffer_size + cache_buffer_size; + + auto buffer_hold_storage = std::make_shared( + std::move(base_storage), total_buffer_size); + R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI); + + s64 base_size = buffer_hold_storage->GetSize(); + R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); + R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); + + auto master_hash_storage = + std::make_shared>(hash_data.fs_data_master_hash.value); + + auto verification_storage = std::make_shared(); + R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + std::array layer_storages{ + std::make_shared(master_hash_storage, sizeof(Hash), 0), + std::make_shared(buffer_hold_storage, hash_region.size, hash_region.offset), + std::make_shared(buffer_hold_storage, data_region.size, data_region.offset), + }; + + R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount, + hash_data.hash_block_size, + buffer_hold_storage->GetBuffer(), hash_buffer_size)); + + *out = std::move(verification_storage); + R_SUCCEED(); +} + Result NcaFileSystemDriver::CreateSha256Storage( VirtualFile* out, VirtualFile base_storage, const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { @@ -1160,6 +1241,7 @@ Result NcaFileSystemDriver::CreateSha256Storage( Result NcaFileSystemDriver::CreateIntegrityVerificationStorage( VirtualFile* out, VirtualFile base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) { + R_RETURN(this->CreateIntegrityVerificationStorageImpl( out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel( @@ -1209,63 +1291,96 @@ Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( VirtualFile* out, VirtualFile base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { - // Validate preconditions. + // Preconditions ASSERT(out != nullptr); ASSERT(base_storage != nullptr); ASSERT(layer_info_offset >= 0); - // Define storage types. - using VerificationStorage = HierarchicalIntegrityVerificationStorage; - using StorageInfo = VerificationStorage::HierarchicalStorageInformation; + if (!Settings::values.disable_nca_verification.GetValue()) { + // Define storage types. + using VerificationStorage = HierarchicalIntegrityVerificationStorage; + using StorageInfo = VerificationStorage::HierarchicalStorageInformation; - // Validate the meta info. - HierarchicalIntegrityVerificationInformation level_hash_info; - std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), - sizeof(level_hash_info)); + // Validate the meta info. + HierarchicalIntegrityVerificationInformation level_hash_info; + std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), + sizeof(level_hash_info)); - R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - // Get the base storage size. - s64 base_storage_size = base_storage->GetSize(); + // Get the base storage size. + s64 base_storage_size = base_storage->GetSize(); - // Create storage info. - StorageInfo storage_info; - for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) { - const auto& layer_info = level_hash_info.info[i]; - R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, + // Create storage info. + StorageInfo storage_info; + for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) { + const auto& layer_info = level_hash_info.info[i]; + R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + + storage_info[i + 1] = std::make_shared( + base_storage, layer_info.size, layer_info_offset + layer_info.offset); + } + + // Set the last layer info. + const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; + const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); + R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, ResultNcaBaseStorageOutOfRangeD); + if (layer_info_offset > 0) { + R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, + ResultRomNcaInvalidIntegrityLayerInfoOffset); + } + storage_info.SetDataStorage(std::make_shared( + std::move(base_storage), layer_info.size, last_layer_info_offset)); - storage_info[i + 1] = std::make_shared( - base_storage, layer_info.size, layer_info_offset + layer_info.offset); + // Make the integrity romfs storage. + auto integrity_storage = std::make_shared(); + R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the integrity storage. + R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, + max_data_cache_entries, max_hash_cache_entries, + buffer_level)); + + // Set the output. + *out = std::move(integrity_storage); + R_SUCCEED(); + } else { + // Read IVFC layout + HierarchicalIntegrityVerificationInformation lhi{}; + std::memcpy(std::addressof(lhi), std::addressof(meta_info.level_hash_info), sizeof(lhi)); + + R_UNLESS(IntegrityMinLayerCount <= lhi.max_layers, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(lhi.max_layers <= IntegrityMaxLayerCount, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + + const auto& data_li = lhi.info[lhi.max_layers - 2]; + + const s64 base_size = base_storage->GetSize(); + + // Compute the data layer window + const s64 data_off = (layer_info_offset > 0) ? 0LL : data_li.offset.Get(); + R_UNLESS(data_off + data_li.size <= base_size, ResultNcaBaseStorageOutOfRangeD); + if (layer_info_offset > 0) { + R_UNLESS(data_off + data_li.size <= layer_info_offset, + ResultRomNcaInvalidIntegrityLayerInfoOffset); + } + + // TODO: Passthrough (temporary compatibility: integrity disabled) + auto data_view = std::make_shared(base_storage, data_li.size, data_off); + R_UNLESS(data_view != nullptr, ResultAllocationMemoryFailedAllocateShared); + + auto passthrough = std::make_shared(std::move(data_view)); + R_UNLESS(passthrough != nullptr, ResultAllocationMemoryFailedAllocateShared); + + *out = std::move(passthrough); + R_SUCCEED(); } - - // Set the last layer info. - const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; - const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); - R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, - ResultNcaBaseStorageOutOfRangeD); - if (layer_info_offset > 0) { - R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, - ResultRomNcaInvalidIntegrityLayerInfoOffset); - } - storage_info.SetDataStorage(std::make_shared( - std::move(base_storage), layer_info.size, last_layer_info_offset)); - - // Make the integrity romfs storage. - auto integrity_storage = std::make_shared(); - R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); - - // Initialize the integrity storage. - R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, - max_data_cache_entries, max_hash_cache_entries, - buffer_level)); - - // Set the output. - *out = std::move(integrity_storage); - R_SUCCEED(); } Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h index 5bc838de64..e09bfc588a 100644 --- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -329,6 +332,10 @@ private: const NcaPatchInfo& patch_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info); + + Result CreateSha3Storage(VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data); + Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage, const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data); diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp index bf5742d39f..cef0f0bb94 100644 --- a/src/core/file_sys/fssystem/fssystem_nca_header.cpp +++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,11 +13,13 @@ u8 NcaHeader::GetProperKeyGeneration() const { } bool NcaPatchInfo::HasIndirectTable() const { - return this->indirect_size != 0; + static constexpr unsigned char BKTR[4] = {'B', 'K', 'T', 'R'}; + return std::memcmp(indirect_header.data(), BKTR, sizeof(BKTR)) == 0; } bool NcaPatchInfo::HasAesCtrExTable() const { - return this->aes_ctr_ex_size != 0; + static constexpr unsigned char BKTR[4] = {'B', 'K', 'T', 'R'}; + return std::memcmp(aes_ctr_ex_header.data(), BKTR, sizeof(BKTR)) == 0; } } // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_passthrough_storage.h b/src/core/file_sys/fssystem/fssystem_passthrough_storage.h new file mode 100644 index 0000000000..8fc6f4962a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_passthrough_storage.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +//TODO: No integrity verification. +class PassthroughStorage final : public IReadOnlyStorage { + YUZU_NON_COPYABLE(PassthroughStorage); + YUZU_NON_MOVEABLE(PassthroughStorage); + +public: + explicit PassthroughStorage(VirtualFile base) : base_(std::move(base)) {} + ~PassthroughStorage() override = default; + + size_t Read(u8* buffer, size_t size, size_t offset) const override { + if (!base_ || size == 0) + return 0; + return base_->Read(buffer, size, offset); + } + size_t GetSize() const override { + return base_ ? base_->GetSize() : 0; + } + +private: + VirtualFile base_{}; +}; + +} // namespace FileSys diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index cf03353f84..80566b7e77 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -1266,6 +1266,10 @@ void KProcess::InitializeInterfaces() { #ifdef HAS_NCE if (this->IsApplication() && Settings::IsNceEnabled()) { + // Register the scoped JIT handler before creating any NCE instances + // so that its signal handler will appear first in the signal chain. + Core::ScopedJitExecution::RegisterHandler(); + for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { m_arm_interfaces[i] = std::make_unique(m_kernel.System(), true, i); } diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 316370266d..495e6e32c2 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -24,6 +27,7 @@ enum class ErrorModule : u32 { HTCS = 4, NCM = 5, DD = 6, + OSDBG = 7, LR = 8, Loader = 9, CMIF = 10, @@ -51,6 +55,7 @@ enum class ErrorModule : u32 { Util = 33, TIPC = 35, ANIF = 37, + CRT = 39, ETHC = 100, I2C = 101, GPIO = 102, @@ -106,6 +111,7 @@ enum class ErrorModule : u32 { Audio = 153, NPNS = 154, NPNSHTTPSTREAM = 155, + IDLE = 156, ARP = 157, SWKBD = 158, BOOT = 159, @@ -115,6 +121,7 @@ enum class ErrorModule : u32 { Fatal = 163, NIMShop = 164, SPSM = 165, + AOC = 166, BGTC = 167, UserlandCrash = 168, SASBUS = 169, @@ -176,13 +183,22 @@ enum class ErrorModule : u32 { DP2HDMI = 244, Cradle = 245, SProfile = 246, + Icm42607p = 248, NDRM = 250, + Fst2 = 251, + Nex = 306, + NPLN = 321, TSPM = 499, DevMenu = 500, + Nverpt = 520, + Am_StuckMonitor = 521, + Pia = 618, + Eagle = 623, GeneralWebApplet = 800, WifiWebAuthApplet = 809, WhitelistedApplet = 810, ShopN = 811, + Coral = 815 }; /// Encapsulates a Horizon OS error code, allowing it to be separated into its constituent fields. diff --git a/src/core/hle/service/am/applet.cpp b/src/core/hle/service/am/applet.cpp index 59ade29c8e..aa355b06d5 100644 --- a/src/core/hle/service/am/applet.cpp +++ b/src/core/hle/service/am/applet.cpp @@ -1,5 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator +// Project// SPDX-License-Identifier: GPL-2.0-or-later #include "core/core.h" #include "core/hle/service/am/applet.h" @@ -12,7 +15,7 @@ Applet::Applet(Core::System& system, std::unique_ptr process_, bool is_ process(std::move(process_)), hid_registration(system, *process), gpu_error_detected_event(context), friend_invitation_storage_channel_event(context), notification_storage_channel_event(context), health_warning_disappeared_system_event(context), - acquired_sleep_lock_event(context), pop_from_general_channel_event(context), + unknown_event(context), acquired_sleep_lock_event(context), pop_from_general_channel_event(context), library_applet_launchable_event(context), accumulated_suspended_tick_changed_event(context), sleep_lock_event(context), state_changed_event(context) { diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h index 835cfe6ec8..6cc8cdf741 100644 --- a/src/core/hle/service/am/applet.h +++ b/src/core/hle/service/am/applet.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -120,6 +123,7 @@ struct Applet { Event friend_invitation_storage_channel_event; Event notification_storage_channel_event; Event health_warning_disappeared_system_event; + Event unknown_event; Event acquired_sleep_lock_event; Event pop_from_general_channel_event; Event library_applet_launchable_event; diff --git a/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp b/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp index 5a787494a8..d99482a45c 100644 --- a/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp +++ b/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp @@ -18,6 +18,7 @@ IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& sys // clang-format off static const FunctionInfo functions[] = { {100, D<&IAllSystemAppletProxiesService::OpenSystemAppletProxy>, "OpenSystemAppletProxy"}, + {110, D<&IAllSystemAppletProxiesService::OpenSystemAppletProxy>, "OpenSystemAppletProxyEx"}, {200, D<&IAllSystemAppletProxiesService::OpenLibraryAppletProxyOld>, "OpenLibraryAppletProxyOld"}, {201, D<&IAllSystemAppletProxiesService::OpenLibraryAppletProxy>, "OpenLibraryAppletProxy"}, {300, nullptr, "OpenOverlayAppletProxy"}, @@ -25,6 +26,7 @@ IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& sys {400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"}, {410, nullptr, "GetSystemAppletControllerForDebug"}, {450, D<&IAllSystemAppletProxiesService::GetSystemProcessCommonFunctions>, "GetSystemProcessCommonFunctions"}, // 19.0.0+ + {460, D<&IAllSystemAppletProxiesService::GetAppletAlternativeFunctions>, "GetAppletAlternativeFunctions"}, // 20.0.0+ {1000, nullptr, "GetDebugFunctions"}, }; // clang-format on @@ -99,6 +101,14 @@ Result IAllSystemAppletProxiesService::GetSystemProcessCommonFunctions() { R_SUCCEED(); } +Result IAllSystemAppletProxiesService::GetAppletAlternativeFunctions() { + LOG_DEBUG(Service_AM, "(STUBBED) called."); + + // TODO (maufeat) + + R_SUCCEED(); +} + std::shared_ptr IAllSystemAppletProxiesService::GetAppletFromProcessId( ProcessId process_id) { return m_window_system.GetByAppletResourceUserId(process_id.pid); diff --git a/src/core/hle/service/am/service/all_system_applet_proxies_service.h b/src/core/hle/service/am/service/all_system_applet_proxies_service.h index a3111c4c9b..525525c795 100644 --- a/src/core/hle/service/am/service/all_system_applet_proxies_service.h +++ b/src/core/hle/service/am/service/all_system_applet_proxies_service.h @@ -39,6 +39,7 @@ private: InCopyHandle process_handle, InLargeData attribute); Result GetSystemProcessCommonFunctions(); + Result GetAppletAlternativeFunctions(); private: std::shared_ptr GetAppletFromProcessId(ProcessId pid); diff --git a/src/core/hle/service/am/service/applet_common_functions.cpp b/src/core/hle/service/am/service/applet_common_functions.cpp index 1c9cd74533..6a73a896f9 100644 --- a/src/core/hle/service/am/service/applet_common_functions.cpp +++ b/src/core/hle/service/am/service/applet_common_functions.cpp @@ -35,6 +35,7 @@ IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_, {310, nullptr, "IsSystemAppletHomeMenu"}, //19.0.0+ {320, nullptr, "SetGpuTimeSliceBoost"}, //19.0.0+ {321, nullptr, "SetGpuTimeSliceBoostDueToApplication"}, //19.0.0+ + {350, D<&IAppletCommonFunctions::Unknown350>, "Unknown350"} //20.0.0+ }; // clang-format on @@ -70,4 +71,10 @@ Result IAppletCommonFunctions::GetCurrentApplicationId(Out out_application_ R_SUCCEED(); } +Result IAppletCommonFunctions::Unknown350(Out out_unknown) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + *out_unknown = 0; + R_SUCCEED(); +} + } // namespace Service::AM diff --git a/src/core/hle/service/am/service/applet_common_functions.h b/src/core/hle/service/am/service/applet_common_functions.h index 376f85acf7..623efdb7fc 100644 --- a/src/core/hle/service/am/service/applet_common_functions.h +++ b/src/core/hle/service/am/service/applet_common_functions.h @@ -20,6 +20,7 @@ private: Result GetHomeButtonDoubleClickEnabled(Out out_home_button_double_click_enabled); Result SetCpuBoostRequestPriority(s32 priority); Result GetCurrentApplicationId(Out out_application_id); + Result Unknown350(Out out_unknown); const std::shared_ptr applet; }; diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp index 560244c714..b736e2821b 100644 --- a/src/core/hle/service/am/service/application_functions.cpp +++ b/src/core/hle/service/am/service/application_functions.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -85,6 +88,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ {181, nullptr, "UpgradeLaunchRequiredVersion"}, {190, nullptr, "SendServerMaintenanceOverlayNotification"}, {200, nullptr, "GetLastApplicationExitReason"}, + {210, D<&IApplicationFunctions::GetUnknownEvent210>, "Unknown210"}, {500, nullptr, "StartContinuousRecordingFlushForDebug"}, {1000, nullptr, "CreateMovieMaker"}, {1001, D<&IApplicationFunctions::PrepareForJit>, "PrepareForJit"}, @@ -487,6 +491,13 @@ Result IApplicationFunctions::GetHealthWarningDisappearedSystemEvent( R_SUCCEED(); } +Result IApplicationFunctions::GetUnknownEvent210( + OutCopyHandle out_event) { + LOG_DEBUG(Service_AM, "called"); + *out_event = m_applet->unknown_event.GetHandle(); + R_SUCCEED(); +} + Result IApplicationFunctions::PrepareForJit() { LOG_WARNING(Service_AM, "(STUBBED) called"); diff --git a/src/core/hle/service/am/service/application_functions.h b/src/core/hle/service/am/service/application_functions.h index 10025a152b..35b3e9505d 100644 --- a/src/core/hle/service/am/service/application_functions.h +++ b/src/core/hle/service/am/service/application_functions.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -76,6 +79,7 @@ private: Result TryPopFromFriendInvitationStorageChannel(Out> out_storage); Result GetNotificationStorageChannelEvent(OutCopyHandle out_event); Result GetHealthWarningDisappearedSystemEvent(OutCopyHandle out_event); + Result GetUnknownEvent210(OutCopyHandle out_event); Result PrepareForJit(); const std::shared_ptr m_applet; diff --git a/src/core/hle/service/am/service/library_applet_creator.cpp b/src/core/hle/service/am/service/library_applet_creator.cpp index 413388d40a..54790838e0 100644 --- a/src/core/hle/service/am/service/library_applet_creator.cpp +++ b/src/core/hle/service/am/service/library_applet_creator.cpp @@ -113,9 +113,11 @@ std::shared_ptr CreateGuestApplet(Core::System& system, Firmware1700 = 17, Firmware1800 = 18, Firmware1900 = 19, + Firmware2000 = 20, + Firmware2100 = 21, }; - auto process = CreateProcess(system, program_id, Firmware1400, Firmware1900); + auto process = CreateProcess(system, program_id, Firmware1400, Firmware2100); if (!process) { // Couldn't initialize the guest process return {}; diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index f1ddba8231..517ec75743 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -306,6 +306,9 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {3013, nullptr, "IsGameCardEnabled"}, {3014, nullptr, "IsLocalContentShareEnabled"}, {3050, nullptr, "ListAssignELicenseTaskResult"}, + {4022, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4022"}, + {4023, D<&IApplicationManagerInterface::Unknown4023>, "Unknown4023"}, + {4088, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4088"}, {9999, nullptr, "GetApplicationCertificate"}, }; // clang-format on @@ -523,4 +526,17 @@ Result IApplicationManagerInterface::GetApplicationTerminateResult(Out o R_SUCCEED(); } +Result IApplicationManagerInterface::Unknown4022( + OutCopyHandle out_event) { + LOG_WARNING(Service_NS, "(STUBBED) called"); + *out_event = gamecard_update_detection_event.GetHandle(); + R_SUCCEED(); +} + +Result IApplicationManagerInterface::Unknown4023(Out out_result) { + LOG_WARNING(Service_NS, "(STUBBED) called."); + *out_result = 0; + R_SUCCEED(); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h index 2def50bd5c..251f93ee06 100644 --- a/src/core/hle/service/ns/application_manager_interface.h +++ b/src/core/hle/service/ns/application_manager_interface.h @@ -53,6 +53,8 @@ public: u64 application_id); Result CheckApplicationLaunchVersion(u64 application_id); Result GetApplicationTerminateResult(Out out_result, u64 application_id); + Result Unknown4022(OutCopyHandle out_event); + Result Unknown4023(Out out_result); private: KernelHelpers::ServiceContext service_context; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 95bf18dbf7..5f754650d9 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -219,28 +219,55 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(IoctlAllocGpfifoEx& params, DeviceFD fd) { return NvResult::Success; } -NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) { - LOG_DEBUG(Service_NVDRV, "called, class_num={:X}, flags={:X}, obj_id={:X}", params.class_num, - params.flags, params.obj_id); +s32_le nvhost_gpu::GetObjectContextClassNumberIndex(CtxClasses class_number) { + constexpr s32_le invalid_class_number_index = -1; + switch (class_number) { + case CtxClasses::Ctx2D: return 0; + case CtxClasses::Ctx3D: return 1; + case CtxClasses::CtxCompute: return 2; + case CtxClasses::CtxKepler: return 3; + case CtxClasses::CtxDMA: return 4; + case CtxClasses::CtxChannelGPFIFO: return 5; + default: return invalid_class_number_index; + } +} - if (!channel_state->initialized) { +NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) { + LOG_DEBUG(Service_NVDRV, "called, class_num={:#X}, flags={:#X}, obj_id={:#X}", params.class_num, + params.flags, params.obj_id); + + if (!channel_state || !channel_state->initialized) { LOG_CRITICAL(Service_NVDRV, "No address space bound to allocate a object context!"); return NvResult::NotInitialized; } - switch (static_cast(params.class_num)) { - case CtxClasses::Ctx2D: - case CtxClasses::Ctx3D: - case CtxClasses::CtxCompute: - case CtxClasses::CtxKepler: - case CtxClasses::CtxDMA: - case CtxClasses::CtxChannelGPFIFO: - ctxObj_params.push_back(params); - return NvResult::Success; - default: - LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:X}", params.class_num); + std::scoped_lock lk(channel_mutex); + + if (params.flags) { + LOG_WARNING(Service_NVDRV, "non-zero flags={:#X} for class={:#X}", params.flags, + params.class_num); + + constexpr u32 allowed_mask{}; + params.flags = allowed_mask; + } + + s32_le ctx_class_number_index = + GetObjectContextClassNumberIndex(static_cast(params.class_num)); + if (ctx_class_number_index < 0) { + LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:#X}", + params.class_num); return NvResult::BadParameter; } + + if (ctxObjs[ctx_class_number_index].has_value()) { + LOG_ERROR(Service_NVDRV, "Object context for class {:#X} already allocated on this channel", + params.class_num); + return NvResult::AlreadyAllocated; + } + + ctxObjs[ctx_class_number_index] = params; + + return NvResult::Success; } static boost::container::small_vector BuildWaitCommandList( diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index a017cc50d0..fb0a5be959 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -172,7 +172,7 @@ private: s32_le nvmap_fd{}; u64_le user_data{}; IoctlZCullBind zcull_params{}; - std::vector ctxObj_params{}; + std::array, 6> ctxObjs{}; u32_le channel_priority{}; u32_le channel_timeslice{}; @@ -184,9 +184,12 @@ private: NvResult SetChannelPriority(IoctlChannelSetPriority& params); NvResult AllocGPFIFOEx(IoctlAllocGpfifoEx& params, DeviceFD fd); NvResult AllocGPFIFOEx2(IoctlAllocGpfifoEx& params, DeviceFD fd); + + s32_le GetObjectContextClassNumberIndex(CtxClasses class_number); NvResult AllocateObjectContext(IoctlAllocObjCtx& params); NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandList&& entries); + NvResult SubmitGPFIFOBase1(IoctlSubmitGpfifo& params, std::span commands, bool kickoff = false); NvResult SubmitGPFIFOBase2(IoctlSubmitGpfifo& params, diff --git a/src/core/hle/service/pctl/parental_control_service.cpp b/src/core/hle/service/pctl/parental_control_service.cpp index 1d990e66d7..82c65ac1fd 100644 --- a/src/core/hle/service/pctl/parental_control_service.cpp +++ b/src/core/hle/service/pctl/parental_control_service.cpp @@ -80,11 +80,12 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili {1451, D<&IParentalControlService::StartPlayTimer>, "StartPlayTimer"}, {1452, D<&IParentalControlService::StopPlayTimer>, "StopPlayTimer"}, {1453, D<&IParentalControlService::IsPlayTimerEnabled>, "IsPlayTimerEnabled"}, - {1454, nullptr, "GetPlayTimerRemainingTime"}, + {1454, D<&IParentalControlService::GetPlayTimerRemainingTime>, "GetPlayTimerRemainingTime"}, {1455, D<&IParentalControlService::IsRestrictedByPlayTimer>, "IsRestrictedByPlayTimer"}, {1456, D<&IParentalControlService::GetPlayTimerSettingsOld>, "GetPlayTimerSettingsOld"}, {1457, D<&IParentalControlService::GetPlayTimerEventToRequestSuspension>, "GetPlayTimerEventToRequestSuspension"}, {1458, D<&IParentalControlService::IsPlayTimerAlarmDisabled>, "IsPlayTimerAlarmDisabled"}, + {1459, D<&IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo>, "GetPlayTimerRemainingTimeDisplayInfo"}, {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"}, {1472, nullptr, "CancelNetworkRequest"}, {1473, D<&IParentalControlService::GetUnlinkedEvent>, "GetUnlinkedEvent"}, @@ -378,6 +379,12 @@ Result IParentalControlService::IsPlayTimerEnabled(Out out_is_play_timer_e R_SUCCEED(); } +Result IParentalControlService::GetPlayTimerRemainingTime(Out out_remaining_time) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + *out_remaining_time = std::numeric_limits::max(); + R_SUCCEED(); +} + Result IParentalControlService::IsRestrictedByPlayTimer(Out out_is_restricted_by_play_timer) { *out_is_restricted_by_play_timer = false; LOG_WARNING(Service_PCTL, "(STUBBED) called, restricted={}", *out_is_restricted_by_play_timer); @@ -412,6 +419,11 @@ Result IParentalControlService::IsPlayTimerAlarmDisabled(Out out_play_time R_SUCCEED(); } +Result IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo(/* Out 0x18 */) { + LOG_INFO(Service_PCTL, "called"); + R_SUCCEED(); +} + Result IParentalControlService::GetUnlinkedEvent(OutCopyHandle out_event) { LOG_INFO(Service_PCTL, "called"); *out_event = unlinked_event.GetHandle(); diff --git a/src/core/hle/service/pctl/parental_control_service.h b/src/core/hle/service/pctl/parental_control_service.h index 1b1884c4de..9d143fe2e2 100644 --- a/src/core/hle/service/pctl/parental_control_service.h +++ b/src/core/hle/service/pctl/parental_control_service.h @@ -49,10 +49,12 @@ private: Result StartPlayTimer(); Result StopPlayTimer(); Result IsPlayTimerEnabled(Out out_is_play_timer_enabled); + Result GetPlayTimerRemainingTime(Out out_remaining_time); Result IsRestrictedByPlayTimer(Out out_is_restricted_by_play_timer); Result GetPlayTimerSettingsOld(Out out_play_timer_settings); Result GetPlayTimerEventToRequestSuspension(OutCopyHandle out_event); Result IsPlayTimerAlarmDisabled(Out out_play_timer_alarm_disabled); + Result GetPlayTimerRemainingTimeDisplayInfo(); Result GetUnlinkedEvent(OutCopyHandle out_event); Result GetStereoVisionRestriction(Out out_stereo_vision_restriction); Result SetStereoVisionRestriction(bool stereo_vision_restriction); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 08391cd815..0035c626e2 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -61,7 +61,8 @@ struct Memory::Impl { } #ifdef __linux__ - buffer.emplace(system.DeviceMemory().buffer); + heap_tracker.emplace(system.DeviceMemory().buffer); + buffer = std::addressof(*heap_tracker); #else buffer = std::addressof(system.DeviceMemory().buffer); #endif @@ -1023,8 +1024,9 @@ struct Memory::Impl { std::span gpu_dirty_managers; std::mutex sys_core_guard; + std::optional heap_tracker; #ifdef __linux__ - std::optional buffer; + Common::HeapTracker* buffer{}; #else Common::HostMemory* buffer{}; #endif @@ -1228,7 +1230,22 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) { if (rasterizer) { impl->InvalidateGPUMemory(ptr, size); } + +#ifdef __linux__ + if (!rasterizer && mapped) { + impl->buffer->DeferredMapSeparateHeap(GetInteger(vaddr)); + } +#endif + return mapped && ptr != nullptr; } +bool Memory::InvalidateSeparateHeap(void* fault_address) { +#ifdef __linux__ + return impl->buffer->DeferredMapSeparateHeap(static_cast(fault_address)); +#else + return false; +#endif +} + } // namespace Core::Memory diff --git a/src/core/memory.h b/src/core/memory.h index 99108ecf0d..dcca26892b 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -487,8 +487,13 @@ public: * marked as debug or non-debug. */ void MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug); + void SetGPUDirtyManagers(std::span managers); + bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size); + + bool InvalidateSeparateHeap(void* fault_address); + private: Core::System& system; diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt index 9391a71b6c..e5934c941a 100644 --- a/src/dedicated_room/CMakeLists.txt +++ b/src/dedicated_room/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2017 Citra Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later @@ -13,7 +16,7 @@ add_library(yuzu-room STATIC EXCLUDE_FROM_ALL target_link_libraries(yuzu-room PRIVATE common network) if (ENABLE_WEB_SERVICE) - target_compile_definitions(yuzu-room PRIVATE -DENABLE_WEB_SERVICE) + target_compile_definitions(yuzu-room PRIVATE ENABLE_WEB_SERVICE) target_link_libraries(yuzu-room PRIVATE web_service) endif() diff --git a/src/dynarmic/CMakeLists.txt b/src/dynarmic/CMakeLists.txt index 5c28435f72..11a8b620ae 100644 --- a/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + cmake_minimum_required(VERSION 3.12) project(dynarmic LANGUAGES C CXX ASM VERSION 6.7.0) @@ -14,15 +17,28 @@ endif() # Dynarmic project options option(DYNARMIC_ENABLE_CPU_FEATURE_DETECTION "Turning this off causes dynarmic to assume the host CPU doesn't support anything later than SSE3" ON) -option(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT "Enables support for systems that require W^X" OFF) + +if (PLATFORM_OPENBSD) + option(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT "Enables support for systems that require W^X" ON) +else() + option(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT "Enables support for systems that require W^X" OFF) +endif() + option(DYNARMIC_FATAL_ERRORS "Errors are fatal" OFF) option(DYNARMIC_IGNORE_ASSERTS "Ignore asserts" OFF) option(DYNARMIC_TESTS_USE_UNICORN "Enable fuzzing tests against unicorn" OFF) option(DYNARMIC_USE_LLVM "Support disassembly of jitted x86_64 code using LLVM" OFF) -option(DYNARMIC_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) + +if (PLATFORM_OPENBSD) + option(DYNARMIC_USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF) +else() + option(DYNARMIC_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) +endif() + option(DYNARMIC_INSTALL "Install dynarmic headers and CMake files" OFF) option(DYNARMIC_USE_BUNDLED_EXTERNALS "Use all bundled externals (useful when e.g. cross-compiling)" OFF) option(DYNARMIC_WARNINGS_AS_ERRORS "Warnings as errors" ${MASTER_PROJECT}) +option(DYNARMIC_ENABLE_LTO "Enable LTO" OFF) if (NOT DEFINED DYNARMIC_FRONTENDS) set(DYNARMIC_FRONTENDS "A32;A64" CACHE STRING "Selects which frontends to enable") endif() @@ -134,21 +150,9 @@ else() endif() endif() -# Forced use of individual bundled libraries for non-REQUIRED library is possible with e.g. cmake -DCMAKE_DISABLE_FIND_PACKAGE_fmt=ON ... - -if (DYNARMIC_USE_BUNDLED_EXTERNALS) - set(CMAKE_DISABLE_FIND_PACKAGE_biscuit ON) - set(CMAKE_DISABLE_FIND_PACKAGE_fmt ON) - set(CMAKE_DISABLE_FIND_PACKAGE_mcl ON) - set(CMAKE_DISABLE_FIND_PACKAGE_oaknut ON) - set(CMAKE_DISABLE_FIND_PACKAGE_unordered_dense ON) - set(CMAKE_DISABLE_FIND_PACKAGE_xbyak ON) - set(CMAKE_DISABLE_FIND_PACKAGE_Zydis ON) - set(CMAKE_DISABLE_FIND_PACKAGE_Zycore ON) -endif() - find_package(Boost 1.57 REQUIRED) find_package(fmt 9 CONFIG) +find_package(mcl 0.1.12 REQUIRED) if ("arm64" IN_LIST ARCHITECTURE OR DYNARMIC_TESTS) find_package(oaknut 2.0.1 CONFIG) @@ -156,6 +160,8 @@ endif() if ("x86_64" IN_LIST ARCHITECTURE) find_package(xbyak 7 CONFIG) + find_package(zycore REQUIRED) + find_package(zydis 4 REQUIRED) endif() if (DYNARMIC_USE_LLVM) diff --git a/src/dynarmic/externals/CMakeLists.txt b/src/dynarmic/externals/CMakeLists.txt index 23cfd42236..a413d04f25 100644 --- a/src/dynarmic/externals/CMakeLists.txt +++ b/src/dynarmic/externals/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # Explicitly include CPMUtil here since we have a separate cpmfile for dynarmic set(CPMUTIL_JSON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cpmfile.json) include(CPMUtil) @@ -20,69 +23,25 @@ set(BUILD_TESTING OFF) # biscuit if ("riscv" IN_LIST ARCHITECTURE) - add_subdirectory(biscuit) - AddJsonPackage( NAME biscuit BUNDLED_PACKAGE ${DYNARMIC_USE_BUNDLED_EXTERNALS} ) endif() -# catch - -# if (NOT TARGET Catch2::Catch2WithMain) -# if (DYNARMIC_TESTS) -# find_package(Catch2 3.0.1 REQUIRED) -# endif() -# endif() - -# fmt - -# if (NOT TARGET fmt::fmt) -# # fmtlib formatting library -# set(FMT_INSTALL ON) -# add_subdirectory(fmt) -# endif() - # mcl AddJsonPackage( NAME mcl BUNDLED_PACKAGE ${DYNARMIC_USE_BUNDLED_EXTERNALS} ) -# oaknut - -# if (NOT TARGET merry::oaknut) -# if ("arm64" IN_LIST ARCHITECTURE) -# add_subdirectory(oaknut) -# elseif (DYNARMIC_TESTS) -# add_subdirectory(oaknut EXCLUDE_FROM_ALL) -# endif() -# endif() - -# unordered_dense - -AddJsonPackage( - NAME unordered-dense - BUNDLED_PACKAGE ${DYNARMIC_USE_BUNDLED_EXTERNALS} -) - -# xbyak -# uncomment if in an independent repo - -# if (NOT TARGET xbyak::xbyak) -# if ("x86_64" IN_LIST ARCHITECTURE) -# add_subdirectory(xbyak) -# endif() -# endif() - -# zydis - # TODO(crueter): maybe it's just Gentoo but zydis system package really sucks if ("x86_64" IN_LIST ARCHITECTURE) set(CMAKE_DISABLE_FIND_PACKAGE_Doxygen ON) - # TODO(crueter): system zycore doesn't work with zydis - AddJsonPackage(zycore) + AddJsonPackage( + NAME zycore + BUNDLED_PACKAGE ${DYNARMIC_USE_BUNDLED_EXTERNALS} + ) AddJsonPackage( NAME zydis diff --git a/src/dynarmic/externals/cpmfile.json b/src/dynarmic/externals/cpmfile.json index b934856af2..718163baf5 100644 --- a/src/dynarmic/externals/cpmfile.json +++ b/src/dynarmic/externals/cpmfile.json @@ -14,25 +14,14 @@ "MCL_INSTALL OFF" ] }, - "unordered-dense": { - "package": "unordered_dense", - "repo": "Lizzie841/unordered_dense", - "sha": "e59d30b7b1", - "hash": "71eff7bd9ba4b9226967bacd56a8ff000946f8813167cb5664bb01e96fb79e4e220684d824fe9c59c4d1cc98c606f13aff05b7940a1ed8ab3c95d6974ee34fa0", - "find_args": "CONFIG", - "options": [ - "UNORDERED_DENSE_INSTALL OFF" - ] - }, "zycore": { - "package": "Zycore", + "package": "zycore", "repo": "zyantific/zycore-c", "sha": "75a36c45ae", - "hash": "15aa399f39713e042c4345bc3175c82f14dca849fde2a21d4f591f62c43e227b70d868d8bb86beb5f4eb68b1d6bd3792cdd638acf89009e787e3d10ee7401924", - "bundled": true + "hash": "15aa399f39713e042c4345bc3175c82f14dca849fde2a21d4f591f62c43e227b70d868d8bb86beb5f4eb68b1d6bd3792cdd638acf89009e787e3d10ee7401924" }, "zydis": { - "package": "Zydis", + "package": "zydis", "version": "4", "repo": "zyantific/zydis", "sha": "c2d2bab025", diff --git a/src/dynarmic/src/dynarmic/CMakeLists.txt b/src/dynarmic/src/dynarmic/CMakeLists.txt index e060989f82..efae44d917 100644 --- a/src/dynarmic/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/src/dynarmic/CMakeLists.txt @@ -164,7 +164,7 @@ if ("x86_64" IN_LIST ARCHITECTURE) target_link_libraries(dynarmic PRIVATE xbyak::xbyak - Zydis + Zydis::Zydis ) target_architecture_specific_sources(dynarmic "x86_64" @@ -440,6 +440,10 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") endif() target_compile_definitions(dynarmic PRIVATE FMT_USE_USER_DEFINED_LITERALS=1) +if (DYNARMIC_ENABLE_LTO) + set_property(TARGET dynarmic PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() + if (DYNARMIC_USE_PRECOMPILED_HEADERS) set(PRECOMPILED_HEADERS "$<$:${CMAKE_CURRENT_SOURCE_DIR}/ir/ir_emitter.h>") if ("x86_64" IN_LIST ARCHITECTURE) diff --git a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp index 5a33ac7727..d5d5f089ff 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp @@ -225,8 +225,14 @@ bool IsUnderRosetta() { } // anonymous namespace +#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT +static const auto default_cg_mode = Xbyak::DontSetProtectRWE; +#else +static const auto default_cg_mode = nullptr; //Allow RWE +#endif + BlockOfCode::BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function rcp) - : Xbyak::CodeGenerator(total_code_size, nullptr, &s_allocator) + : Xbyak::CodeGenerator(total_code_size, default_cg_mode, &s_allocator) , cb(std::move(cb)) , jsi(jsi) , constant_pool(*this, CONSTANT_POOL_SIZE) diff --git a/src/dynarmic/src/dynarmic/common/lut_from_list.h b/src/dynarmic/src/dynarmic/common/lut_from_list.h index ed9e3dc046..c904e2c041 100644 --- a/src/dynarmic/src/dynarmic/common/lut_from_list.h +++ b/src/dynarmic/src/dynarmic/common/lut_from_list.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* This file is part of the dynarmic project. * Copyright (c) 2018 MerryMage * SPDX-License-Identifier: 0BSD @@ -19,6 +22,16 @@ namespace Dynarmic::Common { +// prevents this function from printing 56,000 character warning messages +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wno-stack-usage" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wno-stack-usage" +#endif + template inline auto GenerateLookupTableFromList(Function f, mcl::mp::list) { #ifdef _MSC_VER @@ -34,4 +47,11 @@ inline auto GenerateLookupTableFromList(Function f, mcl::mp::list) { return MapT(pair_array.begin(), pair_array.end()); } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } // namespace Dynarmic::Common diff --git a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp index 474c2f8404..7c0ba8a890 100644 --- a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp +++ b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp @@ -37,7 +37,7 @@ namespace { struct SpinLockImpl { void Initialize(); - Xbyak::CodeGenerator code; + Xbyak::CodeGenerator code = Xbyak::CodeGenerator(4096, Xbyak::DontSetProtectRWE); void (*lock)(volatile int*); void (*unlock)(volatile int*); diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index d0787b0936..1487033b22 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2022 yuzu Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later @@ -21,7 +24,7 @@ create_target_directory_groups(network) target_link_libraries(network PRIVATE common enet Boost::headers) if (ENABLE_WEB_SERVICE) - target_compile_definitions(network PRIVATE -DENABLE_WEB_SERVICE) + target_compile_definitions(network PRIVATE ENABLE_WEB_SERVICE) target_link_libraries(network PRIVATE web_service) endif() diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 89fe7a35f9..e0f7f82fbe 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -332,7 +332,10 @@ target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS}) add_dependencies(video_core host_shaders) target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) -target_link_libraries(video_core PRIVATE sirit Vulkan::Headers Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator) +target_link_libraries(video_core PRIVATE sirit) + +# Header-only stuff needed by all dependent targets +target_link_libraries(video_core PUBLIC Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator) if (ENABLE_NSIGHT_AFTERMATH) if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK}) diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index 596323fb32..37213912e3 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -102,13 +102,16 @@ constexpr VkPipelineVertexInputStateCreateInfo PIPELINE_VERTEX_INPUT_STATE_CREAT .vertexAttributeDescriptionCount = 0, .pVertexAttributeDescriptions = nullptr, }; -constexpr VkPipelineInputAssemblyStateCreateInfo PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - .primitiveRestartEnable = VK_FALSE, -}; + +VkPipelineInputAssemblyStateCreateInfo GetPipelineInputAssemblyStateCreateInfo(const Device& device) { + return VkPipelineInputAssemblyStateCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = device.IsMoltenVK() ? VK_TRUE : VK_FALSE, + }; +} constexpr VkPipelineViewportStateCreateInfo PIPELINE_VIEWPORT_STATE_CREATE_INFO{ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pNext = nullptr, @@ -802,6 +805,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe .pAttachments = &blend_attachment, .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, }; + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); blit_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -809,7 +813,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, @@ -833,6 +837,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip } blit_depth_stencil_keys.push_back(key); const std::array stages = MakeStages(*full_screen_vert, *blit_depth_stencil_frag); + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); blit_depth_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -840,7 +845,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, @@ -885,6 +890,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel .pAttachments = &color_blend_attachment_state, .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, }; + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); clear_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -892,7 +898,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, @@ -940,6 +946,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( .minDepthBounds = 0.0f, .maxDepthBounds = 0.0f, }; + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); clear_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -947,7 +954,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, @@ -970,6 +977,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend } VkShaderModule frag_shader = *convert_float_to_depth_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -977,7 +985,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, @@ -999,6 +1007,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend } VkShaderModule frag_shader = *convert_depth_to_float_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -1006,7 +1015,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, @@ -1029,6 +1038,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren return; } const std::array stages = MakeStages(*full_screen_vert, *module); + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -1036,7 +1046,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, @@ -1070,6 +1080,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende VkShaderModule frag_shader = is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -1077,7 +1088,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, diff --git a/src/video_core/renderer_vulkan/present/util.cpp b/src/video_core/renderer_vulkan/present/util.cpp index 6874bbae99..07b6a41c5c 100644 --- a/src/video_core/renderer_vulkan/present/util.cpp +++ b/src/video_core/renderer_vulkan/present/util.cpp @@ -400,12 +400,12 @@ static vk::Pipeline CreateWrappedPipelineImpl( .pVertexAttributeDescriptions = nullptr, }; - constexpr VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{ + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .pNext = nullptr, .flags = 0, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, - .primitiveRestartEnable = VK_FALSE, + .primitiveRestartEnable = device.IsMoltenVK() ? VK_TRUE : VK_FALSE, }; constexpr VkPipelineViewportStateCreateInfo viewport_state_ci{ diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 0226eb2c14..dc068c5e52 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -635,14 +635,16 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .flags = 0, .topology = input_assembly_topology, .primitiveRestartEnable = - dynamic.primitive_restart_enable != 0 && + // MoltenVK/Metal always has primitive restart enabled and cannot disable it + device.IsMoltenVK() ? VK_TRUE : + (dynamic.primitive_restart_enable != 0 && ((input_assembly_topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST && device.IsTopologyListPrimitiveRestartSupported()) || SupportsPrimitiveRestart(input_assembly_topology) || (input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST && device.IsPatchListPrimitiveRestartSupported())) ? VK_TRUE - : VK_FALSE, + : VK_FALSE), }; const VkPipelineTessellationStateCreateInfo tessellation_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO, diff --git a/src/video_core/vulkan_common/vma.h b/src/video_core/vulkan_common/vma.h index 6e25aa1bdf..911c1114b2 100644 --- a/src/video_core/vulkan_common/vma.h +++ b/src/video_core/vulkan_common/vma.h @@ -1,3 +1,5 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,4 +10,4 @@ #define VMA_STATIC_VULKAN_FUNCTIONS 0 #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 -#include +#include "vk_mem_alloc.h" diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index cfa88850a0..6fdf1e7874 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -725,6 +725,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR dynamic_state3_enables = true; } + if (is_mvk && Settings::values.dyna_state.GetValue() != 0) { + LOG_WARNING(Render_Vulkan, "MoltenVK detected: Forcing dynamic state to 0 to prevent black screen issues"); + Settings::values.dyna_state.SetValue(0); + } + if (Settings::values.dyna_state.GetValue() == 0) { must_emulate_scaled_formats = true; LOG_INFO(Render_Vulkan, "Dynamic state is disabled (dyna_state = 0), forcing scaled format emulation ON"); @@ -753,18 +758,24 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR functions.vkGetInstanceProcAddr = dld.vkGetInstanceProcAddr; functions.vkGetDeviceProcAddr = dld.vkGetDeviceProcAddr; - const VmaAllocatorCreateInfo allocator_info = { - .flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT, - .physicalDevice = physical, - .device = *logical, - .preferredLargeHeapBlockSize = 0, - .pAllocationCallbacks = nullptr, - .pDeviceMemoryCallbacks = nullptr, - .pHeapSizeLimit = nullptr, - .pVulkanFunctions = &functions, - .instance = instance, - .vulkanApiVersion = VK_API_VERSION_1_1, - .pTypeExternalMemoryHandleTypes = nullptr, + VmaAllocatorCreateFlags flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; + if (extensions.memory_budget) { + flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; + } + const VmaAllocatorCreateInfo allocator_info{ + .flags = flags, + .physicalDevice = physical, + .device = *logical, + .preferredLargeHeapBlockSize = is_integrated + ? (64u * 1024u * 1024u) + : (256u * 1024u * 1024u), + .pAllocationCallbacks = nullptr, + .pDeviceMemoryCallbacks = nullptr, + .pHeapSizeLimit = nullptr, + .pVulkanFunctions = &functions, + .instance = instance, + .vulkanApiVersion = ApiVersion(), + .pTypeExternalMemoryHandleTypes = nullptr, }; vk::Check(vmaCreateAllocator(&allocator_info, &allocator)); @@ -1090,8 +1101,15 @@ bool Device::GetSuitability(bool requires_swapchain) { // Some features are mandatory. Check those. #define CHECK_FEATURE(feature, name) \ if (!features.feature.name) { \ - LOG_ERROR(Render_Vulkan, "Missing required feature {}", #name); \ - suitable = false; \ + if (IsMoltenVK() && (strcmp(#name, "geometryShader") == 0 || \ + strcmp(#name, "logicOp") == 0 || \ + strcmp(#name, "shaderCullDistance") == 0 || \ + strcmp(#name, "wideLines") == 0)) { \ + LOG_INFO(Render_Vulkan, "MoltenVK missing feature {} - using fallback", #name); \ + } else { \ + LOG_ERROR(Render_Vulkan, "Missing required feature {}", #name); \ + suitable = false; \ + } \ } #define LOG_FEATURE(feature, name) \ @@ -1378,13 +1396,13 @@ void Device::CollectPhysicalMemoryInfo() { device_access_memory += mem_properties.memoryHeaps[element].size; } if (!is_integrated) { - const u64 reserve_memory = std::min(device_access_memory / 4, 2_GiB); + const u64 reserve_memory = std::min(device_access_memory / 8, 1_GiB); device_access_memory -= reserve_memory; if (Settings::values.vram_usage_mode.GetValue() != Settings::VramUsageMode::Aggressive) { // Account for resolution scaling in memory limits - const size_t normal_memory = 8_GiB; - const size_t scaler_memory = 2_GiB * Settings::values.resolution_info.ScaleUp(1); + const size_t normal_memory = 6_GiB; + const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1); device_access_memory = std::min(device_access_memory, normal_memory + scaler_memory); } @@ -1393,7 +1411,7 @@ void Device::CollectPhysicalMemoryInfo() { } const s64 available_memory = static_cast(device_access_memory - device_initial_usage); device_access_memory = static_cast(std::max( - std::min(available_memory - 4_GiB, 6_GiB), std::min(local_memory, 6_GiB))); + std::min(available_memory - 8_GiB, 6_GiB), std::min(local_memory, 6_GiB))); } void Device::CollectToolingInfo() { diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 9b78f2e599..bd54144480 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -717,6 +717,10 @@ public: return properties.driver.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY; } + bool IsMoltenVK() const noexcept { + return properties.driver.driverID == VK_DRIVER_ID_MOLTENVK; + } + NvidiaArchitecture GetNvidiaArch() const noexcept { return nvidia_arch; } diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 2e37615f99..4ab420afea 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -6,7 +6,10 @@ #include #include +#include #include +#include +#include #include #include "common/alignment.h" @@ -21,379 +24,302 @@ #include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -namespace { -struct Range { - u64 begin; - u64 end; + namespace { - [[nodiscard]] bool Contains(u64 iterator, u64 size) const noexcept { - return iterator < end && begin < iterator + size; - } -}; +// Helpers translating MemoryUsage to flags/usage -[[nodiscard]] u64 AllocationChunkSize(u64 required_size) { - static constexpr std::array sizes{ - 0x1000ULL << 10, 0x1400ULL << 10, 0x1800ULL << 10, 0x1c00ULL << 10, 0x2000ULL << 10, - 0x3200ULL << 10, 0x4000ULL << 10, 0x6000ULL << 10, 0x8000ULL << 10, 0xA000ULL << 10, - 0x10000ULL << 10, 0x18000ULL << 10, 0x20000ULL << 10, - }; - static_assert(std::is_sorted(sizes.begin(), sizes.end())); - - const auto it = std::ranges::lower_bound(sizes, required_size); - return it != sizes.end() ? *it : Common::AlignUp(required_size, 4ULL << 20); -} - -[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) { - switch (usage) { - case MemoryUsage::DeviceLocal: - return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - case MemoryUsage::Upload: - return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - case MemoryUsage::Download: - return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | - VK_MEMORY_PROPERTY_HOST_CACHED_BIT; - case MemoryUsage::Stream: - return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - } - ASSERT_MSG(false, "Invalid memory usage={}", usage); - return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; -} - -[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { - return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT - : VkMemoryPropertyFlagBits{}; -} - -[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) { - switch (usage) { - case MemoryUsage::Upload: - case MemoryUsage::Stream: - return VMA_ALLOCATION_CREATE_MAPPED_BIT | - VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; - case MemoryUsage::Download: - return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; - case MemoryUsage::DeviceLocal: - return {}; - } - return {}; -} - -[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) { - switch (usage) { - case MemoryUsage::DeviceLocal: - case MemoryUsage::Stream: - return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - case MemoryUsage::Upload: - case MemoryUsage::Download: - return VMA_MEMORY_USAGE_AUTO_PREFER_HOST; - } - return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; -} - -} // Anonymous namespace - -class MemoryAllocation { -public: - explicit MemoryAllocation(MemoryAllocator* const allocator_, vk::DeviceMemory memory_, - VkMemoryPropertyFlags properties, u64 allocation_size_, u32 type) - : allocator{allocator_}, memory{std::move(memory_)}, allocation_size{allocation_size_}, - property_flags{properties}, shifted_memory_type{1U << type} {} - - MemoryAllocation& operator=(const MemoryAllocation&) = delete; - MemoryAllocation(const MemoryAllocation&) = delete; - - MemoryAllocation& operator=(MemoryAllocation&&) = delete; - MemoryAllocation(MemoryAllocation&&) = delete; - - [[nodiscard]] std::optional Commit(VkDeviceSize size, VkDeviceSize alignment) { - const std::optional alloc = FindFreeRegion(size, alignment); - if (!alloc) { - // Signal out of memory, it'll try to do more allocations. - return std::nullopt; + [[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) { + switch (usage) { + case MemoryUsage::DeviceLocal: + return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + case MemoryUsage::Upload: + return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + case MemoryUsage::Download: + return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + case MemoryUsage::Stream: + return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + } + ASSERT_MSG(false, "Invalid memory usage={}", usage); + return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; } - const Range range{ - .begin = *alloc, - .end = *alloc + size, + + [[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { + return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + : VkMemoryPropertyFlagBits{}; + } + + [[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) { + switch (usage) { + case MemoryUsage::Upload: + case MemoryUsage::Stream: + return VMA_ALLOCATION_CREATE_MAPPED_BIT | + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + case MemoryUsage::Download: + return VMA_ALLOCATION_CREATE_MAPPED_BIT | + VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; + case MemoryUsage::DeviceLocal: + return {}; + } + return {}; + } + + [[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) { + switch (usage) { + case MemoryUsage::DeviceLocal: + case MemoryUsage::Stream: + return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + case MemoryUsage::Upload: + case MemoryUsage::Download: + return VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + } + return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + } + + +// This avoids calling vkGetBufferMemoryRequirements* directly. + template + static VkBuffer GetVkHandleFromBuffer(const T &buf) { + if constexpr (requires { static_cast(buf); }) { + return static_cast(buf); + } else if constexpr (requires {{ buf.GetHandle() } -> std::convertible_to; }) { + return buf.GetHandle(); + } else if constexpr (requires {{ buf.Handle() } -> std::convertible_to; }) { + return buf.Handle(); + } else if constexpr (requires {{ buf.vk_handle() } -> std::convertible_to; }) { + return buf.vk_handle(); + } else { + static_assert(sizeof(T) == 0, "Cannot extract VkBuffer handle from vk::Buffer"); + return VK_NULL_HANDLE; + } + } + + } // namespace + +//MemoryCommit is now VMA-backed + MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a, + const VmaAllocationInfo &info) noexcept + : allocator{alloc}, allocation{a}, memory{info.deviceMemory}, + offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {} + + MemoryCommit::~MemoryCommit() { Release(); } + + MemoryCommit::MemoryCommit(MemoryCommit &&rhs) noexcept + : allocator{std::exchange(rhs.allocator, nullptr)}, + allocation{std::exchange(rhs.allocation, nullptr)}, + memory{std::exchange(rhs.memory, VK_NULL_HANDLE)}, + offset{std::exchange(rhs.offset, 0)}, + size{std::exchange(rhs.size, 0)}, + mapped_ptr{std::exchange(rhs.mapped_ptr, nullptr)} {} + + MemoryCommit &MemoryCommit::operator=(MemoryCommit &&rhs) noexcept { + if (this != &rhs) { + Release(); + allocator = std::exchange(rhs.allocator, nullptr); + allocation = std::exchange(rhs.allocation, nullptr); + memory = std::exchange(rhs.memory, VK_NULL_HANDLE); + offset = std::exchange(rhs.offset, 0); + size = std::exchange(rhs.size, 0); + mapped_ptr = std::exchange(rhs.mapped_ptr, nullptr); + } + return *this; + } + + std::span MemoryCommit::Map() + { + if (!allocation) return {}; + if (!mapped_ptr) { + if (vmaMapMemory(allocator, allocation, &mapped_ptr) != VK_SUCCESS) return {}; + } + const size_t n = static_cast(std::min(size, + std::numeric_limits::max())); + return std::span{static_cast(mapped_ptr), n}; + } + + std::span MemoryCommit::Map() const + { + if (!allocation) return {}; + if (!mapped_ptr) { + void *p = nullptr; + if (vmaMapMemory(allocator, allocation, &p) != VK_SUCCESS) return {}; + const_cast(this)->mapped_ptr = p; + } + const size_t n = static_cast(std::min(size, + std::numeric_limits::max())); + return std::span{static_cast(mapped_ptr), n}; + } + + void MemoryCommit::Unmap() + { + if (allocation && mapped_ptr) { + vmaUnmapMemory(allocator, allocation); + mapped_ptr = nullptr; + } + } + + void MemoryCommit::Release() { + if (allocation && allocator) { + if (mapped_ptr) { + vmaUnmapMemory(allocator, allocation); + mapped_ptr = nullptr; + } + vmaFreeMemory(allocator, allocation); + } + allocation = nullptr; + allocator = nullptr; + memory = VK_NULL_HANDLE; + offset = 0; + size = 0; + } + + MemoryAllocator::MemoryAllocator(const Device &device_) + : device{device_}, allocator{device.GetAllocator()}, + properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, + buffer_image_granularity{ + device_.GetPhysical().GetProperties().limits.bufferImageGranularity} { + + // Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug + if (device.HasDebuggingToolAttached()) + { + using namespace Common::Literals; + ForEachDeviceLocalHostVisibleHeap(device, [this](size_t heap_idx, VkMemoryHeap &heap) { + if (heap.size <= 256_MiB) { + for (u32 t = 0; t < properties.memoryTypeCount; ++t) { + if (properties.memoryTypes[t].heapIndex == heap_idx) { + valid_memory_types &= ~(1u << t); + } + } + } + }); + } + } + + MemoryAllocator::~MemoryAllocator() = default; + + vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const + { + const VmaAllocationCreateInfo alloc_ci = { + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, + .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + .requiredFlags = 0, + .preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + .memoryTypeBits = 0, + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + .priority = 0.f, }; - commits.insert(std::ranges::upper_bound(commits, *alloc, {}, &Range::begin), range); - return std::make_optional(this, *memory, *alloc, *alloc + size); + + VkImage handle{}; + VmaAllocation allocation{}; + vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr)); + return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation, + device.GetDispatchLoader()); } - void Free(u64 begin) { - const auto it = std::ranges::find(commits, begin, &Range::begin); - ASSERT_MSG(it != commits.end(), "Invalid commit"); - commits.erase(it); - if (commits.empty()) { - // Do not call any code involving 'this' after this call, the object will be destroyed - allocator->ReleaseMemory(this); - } + vk::Buffer + MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const + { + const VmaAllocationCreateInfo alloc_ci = { + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), + .usage = MemoryUsageVma(usage), + .requiredFlags = 0, + .preferredFlags = MemoryUsagePreferredVmaFlags(usage), + .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types, + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + .priority = 0.f, + }; + + VkBuffer handle{}; + VmaAllocationInfo alloc_info{}; + VmaAllocation allocation{}; + VkMemoryPropertyFlags property_flags{}; + + vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info)); + vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags); + + u8 *data = reinterpret_cast(alloc_info.pMappedData); + const std::span mapped_data = data ? std::span{data, ci.size} : std::span{}; + const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; + + return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, + is_coherent, + device.GetDispatchLoader()); } - [[nodiscard]] std::span Map() { - if (memory_mapped_span.empty()) { - u8* const raw_pointer = memory.Map(0, allocation_size); - memory_mapped_span = std::span(raw_pointer, allocation_size); - } - return memory_mapped_span; - } + MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage) + { + const auto vma_usage = MemoryUsageVma(usage); + VmaAllocationCreateInfo ci{}; + ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage); + ci.usage = vma_usage; + ci.memoryTypeBits = reqs.memoryTypeBits & valid_memory_types; + ci.requiredFlags = 0; + ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage); - /// Returns whether this allocation is compatible with the arguments. - [[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags flags, u32 type_mask) const { - return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0; - } + VmaAllocation a{}; + VmaAllocationInfo info{}; + VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info); -private: - [[nodiscard]] static constexpr u32 ShiftType(u32 type) { - return 1U << type; - } + if (res != VK_SUCCESS) { + // Relax 1: drop budget constraint + auto ci2 = ci; + ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; + res = vmaAllocateMemory(allocator, &reqs, &ci2, &a, &info); - [[nodiscard]] std::optional FindFreeRegion(u64 size, u64 alignment) noexcept { - ASSERT(std::has_single_bit(alignment)); - const u64 alignment_log2 = std::countr_zero(alignment); - std::optional candidate; - u64 iterator = 0; - auto commit = commits.begin(); - while (iterator + size <= allocation_size) { - candidate = candidate.value_or(iterator); - if (commit == commits.end()) { - break; + // Relax 2: if we preferred DEVICE_LOCAL, drop that preference + if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + auto ci3 = ci2; + ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + res = vmaAllocateMemory(allocator, &reqs, &ci3, &a, &info); } - if (commit->Contains(*candidate, size)) { - candidate = std::nullopt; + } + + vk::Check(res); + return MemoryCommit(allocator, a, info); + } + + MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) { + // Allocate memory appropriate for this buffer automatically + const auto vma_usage = MemoryUsageVma(usage); + + VmaAllocationCreateInfo ci{}; + ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage); + ci.usage = vma_usage; + ci.requiredFlags = 0; + ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage); + ci.pool = VK_NULL_HANDLE; + ci.pUserData = nullptr; + ci.priority = 0.0f; + + const VkBuffer raw = *buffer; + + VmaAllocation a{}; + VmaAllocationInfo info{}; + + // Let VMA infer memory requirements from the buffer + VkResult res = vmaAllocateMemoryForBuffer(allocator, raw, &ci, &a, &info); + + if (res != VK_SUCCESS) { + auto ci2 = ci; + ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; + res = vmaAllocateMemoryForBuffer(allocator, raw, &ci2, &a, &info); + + if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + auto ci3 = ci2; + ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + res = vmaAllocateMemoryForBuffer(allocator, raw, &ci3, &a, &info); } - iterator = Common::AlignUpLog2(commit->end, alignment_log2); - ++commit; } - return candidate; + + vk::Check(res); + vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr)); + return MemoryCommit(allocator, a, info); } - MemoryAllocator* const allocator; ///< Parent memory allocation. - const vk::DeviceMemory memory; ///< Vulkan memory allocation handler. - const u64 allocation_size; ///< Size of this allocation. - const VkMemoryPropertyFlags property_flags; ///< Vulkan memory property flags. - const u32 shifted_memory_type; ///< Shifted Vulkan memory type. - std::vector commits; ///< All commit ranges done from this allocation. - std::span memory_mapped_span; ///< Memory mapped span. Empty if not queried before. -}; - -MemoryCommit::MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_, - u64 end_) noexcept - : allocation{allocation_}, memory{memory_}, begin{begin_}, end{end_} {} - -MemoryCommit::~MemoryCommit() { - Release(); -} - -MemoryCommit& MemoryCommit::operator=(MemoryCommit&& rhs) noexcept { - Release(); - allocation = std::exchange(rhs.allocation, nullptr); - memory = rhs.memory; - begin = rhs.begin; - end = rhs.end; - span = std::exchange(rhs.span, std::span{}); - return *this; -} - -MemoryCommit::MemoryCommit(MemoryCommit&& rhs) noexcept - : allocation{std::exchange(rhs.allocation, nullptr)}, memory{rhs.memory}, begin{rhs.begin}, - end{rhs.end}, span{std::exchange(rhs.span, std::span{})} {} - -std::span MemoryCommit::Map() { - if (span.empty()) { - span = allocation->Map().subspan(begin, end - begin); - } - return span; -} - -void MemoryCommit::Release() { - if (allocation) { - allocation->Free(begin); - } -} - -MemoryAllocator::MemoryAllocator(const Device& device_) - : device{device_}, allocator{device.GetAllocator()}, - properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, - buffer_image_granularity{ - device_.GetPhysical().GetProperties().limits.bufferImageGranularity} { - // GPUs not supporting rebar may only have a region with less than 256MB host visible/device - // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to - // the heap running out of memory. With RenderDoc attached and only a small host/device region, - // only allow the stream buffer in this memory heap. - if (device.HasDebuggingToolAttached()) { - using namespace Common::Literals; - ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) { - if (heap.size <= 256_MiB) { - valid_memory_types &= ~(1u << index); - } - }); - } -} - -MemoryAllocator::~MemoryAllocator() = default; - -vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const { - const VmaAllocationCreateInfo alloc_ci = { - .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, - .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, - .requiredFlags = 0, - .preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - .memoryTypeBits = 0, - .pool = VK_NULL_HANDLE, - .pUserData = nullptr, - .priority = 0.f, - }; - - VkImage handle{}; - VmaAllocation allocation{}; - - vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr)); - - return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation, - device.GetDispatchLoader()); -} - -vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { - const VmaAllocationCreateInfo alloc_ci = { - .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), - .usage = MemoryUsageVma(usage), - .requiredFlags = 0, - .preferredFlags = MemoryUsagePreferredVmaFlags(usage), - .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types, - .pool = VK_NULL_HANDLE, - .pUserData = nullptr, - .priority = 0.f, - }; - - VkBuffer handle{}; - VmaAllocationInfo alloc_info{}; - VmaAllocation allocation{}; - VkMemoryPropertyFlags property_flags{}; - - vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info)); - vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags); - - u8* data = reinterpret_cast(alloc_info.pMappedData); - const std::span mapped_data = data ? std::span{data, ci.size} : std::span{}; - const bool is_coherent = property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - - return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent, - device.GetDispatchLoader()); -} - -MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) { - // Find the fastest memory flags we can afford with the current requirements - const u32 type_mask = requirements.memoryTypeBits; - const VkMemoryPropertyFlags usage_flags = MemoryUsagePropertyFlags(usage); - const VkMemoryPropertyFlags flags = MemoryPropertyFlags(type_mask, usage_flags); - if (std::optional commit = TryCommit(requirements, flags)) { - return std::move(*commit); - } - // Commit has failed, allocate more memory. - const u64 chunk_size = AllocationChunkSize(requirements.size); - if (!TryAllocMemory(flags, type_mask, chunk_size)) { - // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory. - throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY); - } - // Commit again, this time it won't fail since there's a fresh allocation above. - // If it does, there's a bug. - return TryCommit(requirements, flags).value(); - } - -bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) { - const auto type_opt = FindType(flags, type_mask); - if (!type_opt) { - return false; - } - - // Adreno stands firm - const u64 aligned_size = (device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) ? - Common::AlignUp(size, 4096) : - size; - - vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({ - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .pNext = nullptr, - .allocationSize = aligned_size, - .memoryTypeIndex = *type_opt, - }); - - if (!memory) { - return false; - } - - allocations.push_back( - std::make_unique(this, std::move(memory), flags, aligned_size, *type_opt)); - return true; -} - -void MemoryAllocator::ReleaseMemory(MemoryAllocation* alloc) { - const auto it = std::ranges::find(allocations, alloc, &std::unique_ptr::get); - ASSERT(it != allocations.end()); - allocations.erase(it); -} - -std::optional MemoryAllocator::TryCommit(const VkMemoryRequirements& requirements, - VkMemoryPropertyFlags flags) { - // Conservative, spec-compliant alignment for suballocation - VkDeviceSize eff_align = requirements.alignment; - const auto& limits = device.GetPhysical().GetProperties().limits; - if ((flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && - !(flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { - // Non-coherent memory must be invalidated on atom boundary - if (limits.nonCoherentAtomSize > eff_align) eff_align = limits.nonCoherentAtomSize; - } - // Separate buffers to avoid stalls on tilers - if (buffer_image_granularity > eff_align) { - eff_align = buffer_image_granularity; - } - eff_align = std::bit_ceil(eff_align); - - for (auto& allocation : allocations) { - if (!allocation->IsCompatible(flags, requirements.memoryTypeBits)) { - continue; - } - if (auto commit = allocation->Commit(requirements.size, eff_align)) { - return commit; - } - } - if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) { - // Look for non device local commits on failure - return TryCommit(requirements, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - } - return std::nullopt; -} - -VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask, - VkMemoryPropertyFlags flags) const { - if (FindType(flags, type_mask)) { - // Found a memory type with those requirements - return flags; - } - if ((flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0) { - // Remove host cached bit in case it's not supported - return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_HOST_CACHED_BIT); - } - if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) { - // Remove device local, if it's not supported by the requested resource - return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - } - ASSERT_MSG(false, "No compatible memory types found"); - return 0; -} - -std::optional MemoryAllocator::FindType(VkMemoryPropertyFlags flags, u32 type_mask) const { - for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) { - const VkMemoryPropertyFlags type_flags = properties.memoryTypes[type_index].propertyFlags; - if ((type_mask & (1U << type_index)) != 0 && (type_flags & flags) == flags) { - // The type matches in type and in the wanted properties. - return type_index; - } - } - // Failed to find index - return std::nullopt; -} - } // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h index 38a182bcba..581f2e66d2 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.h +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,138 +9,134 @@ #include #include #include + #include "common/common_types.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" - -VK_DEFINE_HANDLE(VmaAllocator) +#include "video_core/vulkan_common/vma.h" namespace Vulkan { -class Device; -class MemoryMap; -class MemoryAllocation; + class Device; /// Hints and requirements for the backing memory type of a commit -enum class MemoryUsage { - DeviceLocal, ///< Requests device local host visible buffer, falling back to device local - ///< memory. - Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads - Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks - Stream, ///< Requests device local host visible buffer, falling back host memory. -}; + enum class MemoryUsage { + DeviceLocal, ///< Requests device local host visible buffer, falling back to device local memory. + Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads + Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks + Stream, ///< Requests device local host visible buffer, falling back host memory. + }; -template -void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) { - auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties; - for (size_t i = 0; i < memory_props.memoryTypeCount; i++) { - auto& memory_type = memory_props.memoryTypes[i]; - if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && - (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) { - f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]); + template + void ForEachDeviceLocalHostVisibleHeap(const Device &device, F &&f) { + auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties; + for (size_t i = 0; i < memory_props.memoryTypeCount; i++) { + auto &memory_type = memory_props.memoryTypes[i]; + if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && + (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) { + f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]); + } } } -} -/// Ownership handle of a memory commitment. -/// Points to a subregion of a memory allocation. -class MemoryCommit { -public: - explicit MemoryCommit() noexcept = default; - explicit MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_, - u64 end_) noexcept; - ~MemoryCommit(); +/// Ownership handle of a memory commitment (real VMA allocation). + class MemoryCommit { + public: + MemoryCommit() noexcept = default; - MemoryCommit& operator=(MemoryCommit&&) noexcept; - MemoryCommit(MemoryCommit&&) noexcept; + MemoryCommit(VmaAllocator allocator, VmaAllocation allocation, + const VmaAllocationInfo &info) noexcept; - MemoryCommit& operator=(const MemoryCommit&) = delete; - MemoryCommit(const MemoryCommit&) = delete; + ~MemoryCommit(); - /// Returns a host visible memory map. - /// It will map the backing allocation if it hasn't been mapped before. - std::span Map(); + MemoryCommit(const MemoryCommit &) = delete; - /// Returns the Vulkan memory handler. - VkDeviceMemory Memory() const { - return memory; - } + MemoryCommit &operator=(const MemoryCommit &) = delete; - /// Returns the start position of the commit relative to the allocation. - VkDeviceSize Offset() const { - return static_cast(begin); - } + MemoryCommit(MemoryCommit &&) noexcept; -private: - void Release(); + MemoryCommit &operator=(MemoryCommit &&) noexcept; - MemoryAllocation* allocation{}; ///< Pointer to the large memory allocation. - VkDeviceMemory memory{}; ///< Vulkan device memory handler. - u64 begin{}; ///< Beginning offset in bytes to where the commit exists. - u64 end{}; ///< Offset in bytes where the commit ends. - std::span span; ///< Host visible memory span. Empty if not queried before. -}; + [[nodiscard]] std::span Map(); + + [[nodiscard]] std::span Map() const; + + void Unmap(); + + explicit operator bool() const noexcept { return allocation != nullptr; } + + VkDeviceMemory Memory() const noexcept { return memory; } + + VkDeviceSize Offset() const noexcept { return offset; } + + VkDeviceSize Size() const noexcept { return size; } + + VmaAllocation Allocation() const noexcept { return allocation; } + + private: + void Release(); + + VmaAllocator allocator{}; ///< VMA allocator + VmaAllocation allocation{}; ///< VMA allocation handle + VkDeviceMemory memory{}; ///< Underlying VkDeviceMemory chosen by VMA + VkDeviceSize offset{}; ///< Offset of this allocation inside VkDeviceMemory + VkDeviceSize size{}; ///< Size of the allocation + void *mapped_ptr{}; ///< Optional persistent mapped pointer + }; /// Memory allocator container. /// Allocates and releases memory allocations on demand. -class MemoryAllocator { - friend MemoryAllocation; + class MemoryAllocator { + public: + /** + * Construct memory allocator + * + * @param device_ Device to allocate from + * + * @throw vk::Exception on failure + */ + explicit MemoryAllocator(const Device &device_); -public: - /** - * Construct memory allocator - * - * @param device_ Device to allocate from - * - * @throw vk::Exception on failure - */ - explicit MemoryAllocator(const Device& device_); - ~MemoryAllocator(); + ~MemoryAllocator(); - MemoryAllocator& operator=(const MemoryAllocator&) = delete; - MemoryAllocator(const MemoryAllocator&) = delete; + MemoryAllocator &operator=(const MemoryAllocator &) = delete; - vk::Image CreateImage(const VkImageCreateInfo& ci) const; + MemoryAllocator(const MemoryAllocator &) = delete; - vk::Buffer CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const; + vk::Image CreateImage(const VkImageCreateInfo &ci) const; - /** - * Commits a memory with the specified requirements. - * - * @param requirements Requirements returned from a Vulkan call. - * @param usage Indicates how the memory will be used. - * - * @returns A memory commit. - */ - MemoryCommit Commit(const VkMemoryRequirements& requirements, MemoryUsage usage); + vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const; - /// Commits memory required by the buffer and binds it. - MemoryCommit Commit(const vk::Buffer& buffer, MemoryUsage usage); + /** + * Commits a memory with the specified requirements. + * + * @param requirements Requirements returned from a Vulkan call. + * @param usage Indicates how the memory will be used. + * + * @returns A memory commit. + */ + MemoryCommit Commit(const VkMemoryRequirements &requirements, MemoryUsage usage); -private: - /// Tries to allocate a chunk of memory. - bool TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size); + /// Commits memory required by the buffer and binds it (for buffers created outside VMA). + MemoryCommit Commit(const vk::Buffer &buffer, MemoryUsage usage); - /// Releases a chunk of memory. - void ReleaseMemory(MemoryAllocation* alloc); + private: + static bool IsAutoUsage(VmaMemoryUsage u) noexcept { + switch (u) { + case VMA_MEMORY_USAGE_AUTO: + case VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE: + case VMA_MEMORY_USAGE_AUTO_PREFER_HOST: + return true; + default: + return false; + } + } - /// Tries to allocate a memory commit. - std::optional TryCommit(const VkMemoryRequirements& requirements, - VkMemoryPropertyFlags flags); - - /// Returns the fastest compatible memory property flags from the wanted flags. - VkMemoryPropertyFlags MemoryPropertyFlags(u32 type_mask, VkMemoryPropertyFlags flags) const; - - /// Returns index to the fastest memory type compatible with the passed requirements. - std::optional FindType(VkMemoryPropertyFlags flags, u32 type_mask) const; - - const Device& device; ///< Device handle. - VmaAllocator allocator; ///< Vma allocator. - const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties. - std::vector> allocations; ///< Current allocations. - VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers - // and optimal images - u32 valid_memory_types{~0u}; -}; + const Device &device; ///< Device handle. + VmaAllocator allocator; ///< VMA allocator. + const VkPhysicalDeviceMemoryProperties properties; ///< Physical device memory properties. + VkDeviceSize buffer_image_granularity; ///< Adjacent buffer/image granularity + u32 valid_memory_types{~0u}; + }; } // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 106630182f..949b91499d 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -580,6 +580,7 @@ DescriptorSets DescriptorPool::Allocate(const VkDescriptorSetAllocateInfo& ai) c case VK_SUCCESS: return DescriptorSets(std::move(sets), num, owner, handle, *dld); case VK_ERROR_OUT_OF_POOL_MEMORY: + case VK_ERROR_FRAGMENTED_POOL: return {}; default: throw Exception(result); @@ -604,6 +605,7 @@ CommandBuffers CommandPool::Allocate(std::size_t num_buffers, VkCommandBufferLev case VK_SUCCESS: return CommandBuffers(std::move(buffers), num_buffers, owner, handle, *dld); case VK_ERROR_OUT_OF_POOL_MEMORY: + case VK_ERROR_FRAGMENTED_POOL: return {}; default: throw Exception(result); diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0ce8f3b898..cf87d5c997 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -251,9 +251,7 @@ if (YUZU_CRASH_DUMPS) breakpad.cpp breakpad.h ) - target_link_libraries(yuzu PRIVATE libbreakpad_client) - target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS) endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") @@ -401,7 +399,6 @@ target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json) target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) -target_link_libraries(yuzu PRIVATE Vulkan::Headers) if (NOT WIN32) target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) endif() @@ -416,24 +413,24 @@ endif() target_compile_definitions(yuzu PRIVATE # Use QStringBuilder for string concatenation to reduce # the overall number of temporary strings created. - -DQT_USE_QSTRINGBUILDER + QT_USE_QSTRINGBUILDER # Disable implicit conversions from/to C strings - -DQT_NO_CAST_FROM_ASCII - -DQT_NO_CAST_TO_ASCII + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII # Disable implicit type narrowing in signal/slot connect() calls. - -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT + QT_NO_NARROWING_CONVERSIONS_IN_CONNECT # Disable unsafe overloads of QProcess' start() function. - -DQT_NO_PROCESS_COMBINED_ARGUMENT_START + QT_NO_PROCESS_COMBINED_ARGUMENT_START # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. - -DQT_NO_URL_CAST_FROM_STRING + QT_NO_URL_CAST_FROM_STRING ) if (YUZU_ENABLE_COMPATIBILITY_REPORTING) - target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING) + target_compile_definitions(yuzu PRIVATE YUZU_ENABLE_COMPATIBILITY_REPORTING) endif() if (USE_DISCORD_PRESENCE) @@ -441,22 +438,22 @@ if (USE_DISCORD_PRESENCE) discord_impl.cpp discord_impl.h ) - target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network) - target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) + target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt6::Network) + target_compile_definitions(yuzu PRIVATE USE_DISCORD_PRESENCE) endif() if (ENABLE_WEB_SERVICE) - target_compile_definitions(yuzu PRIVATE -DENABLE_WEB_SERVICE) + target_compile_definitions(yuzu PRIVATE ENABLE_WEB_SERVICE) endif() if (YUZU_USE_QT_MULTIMEDIA) - target_link_libraries(yuzu PRIVATE Qt${QT_MAJOR_VERSION}::Multimedia) - target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_MULTIMEDIA) + target_link_libraries(yuzu PRIVATE Qt6::Multimedia) + target_compile_definitions(yuzu PRIVATE YUZU_USE_QT_MULTIMEDIA) endif () if (YUZU_USE_QT_WEB_ENGINE) - target_link_libraries(yuzu PRIVATE Qt${QT_MAJOR_VERSION}::WebEngineCore Qt${QT_MAJOR_VERSION}::WebEngineWidgets) - target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) + target_link_libraries(yuzu PRIVATE Qt6::WebEngineCore Qt6::WebEngineWidgets) + target_compile_definitions(yuzu PRIVATE YUZU_USE_QT_WEB_ENGINE) endif () if(UNIX AND NOT APPLE) @@ -468,6 +465,7 @@ if (WIN32 AND NOT YUZU_USE_BUNDLED_QT AND QT_VERSION VERSION_GREATER_EQUAL 6) add_custom_command(TARGET yuzu POST_BUILD COMMAND ${WINDEPLOYQT_EXECUTABLE} "${YUZU_EXE_DIR}/eden.exe" --dir "${YUZU_EXE_DIR}" --libdir "${YUZU_EXE_DIR}" --plugindir "${YUZU_EXE_DIR}/plugins" --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --no-translations --verbose 0) endif() +# TODO(crueter): this can be done with system qt in a better way if (YUZU_USE_BUNDLED_QT) include(CopyYuzuQt6Deps) copy_yuzu_Qt6_deps(yuzu) diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index 8245c12ba2..a287ea16df 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -17,15 +17,16 @@ #include "yuzu/applets/qt_web_browser_scripts.h" #endif +#include "yuzu/applets/qt_web_browser.h" +#include "yuzu/main.h" + +#ifdef YUZU_USE_QT_WEB_ENGINE + #include "common/fs/path_util.h" #include "core/core.h" #include "input_common/drivers/keyboard.h" -#include "yuzu/applets/qt_web_browser.h" -#include "yuzu/main.h" #include "yuzu/util/url_request_interceptor.h" -#ifdef YUZU_USE_QT_WEB_ENGINE - namespace { constexpr int HIDButtonToKey(Core::HID::NpadButton button) { diff --git a/src/yuzu/breakpad.cpp b/src/yuzu/breakpad.cpp index 82b5deb51e..d7aed569b7 100644 --- a/src/yuzu/breakpad.cpp +++ b/src/yuzu/breakpad.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,6 +11,8 @@ #include #elif defined(__linux__) #include +#elif defined(__sun__) +#include #else #error Minidump creation not supported on this platform #endif @@ -49,7 +54,7 @@ static void PruneDumpDirectory(const std::filesystem::path& dump_path) { } } -#if defined(__linux__) +#ifdef __linux__ [[noreturn]] bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { // Prevent time- and space-consuming core dumps from being generated, as we have @@ -63,7 +68,7 @@ void InstallCrashHandler() { const auto dump_path = GetEdenPath(Common::FS::EdenPath::CrashDumpsDir); PruneDumpDirectory(dump_path); -#if defined(_WIN32) +#ifdef _WIN32 // TODO: If we switch to MinGW builds for Windows, this needs to be wrapped in a C API. static google_breakpad::ExceptionHandler eh{dump_path, nullptr, nullptr, nullptr, google_breakpad::ExceptionHandler::HANDLER_ALL}; @@ -71,6 +76,8 @@ void InstallCrashHandler() { static google_breakpad::MinidumpDescriptor descriptor{dump_path}; static google_breakpad::ExceptionHandler eh{descriptor, nullptr, DumpCallback, nullptr, true, -1}; +#elif defined(__sun__) + static google_breakpad::ExceptionHandler eh{dump_path, nullptr, DumpCallback, nullptr, true}; #endif } diff --git a/src/yuzu/configuration/configure_graphics_extensions.cpp b/src/yuzu/configuration/configure_graphics_extensions.cpp index c8dee6b073..322fa9ea08 100644 --- a/src/yuzu/configuration/configure_graphics_extensions.cpp +++ b/src/yuzu/configuration/configure_graphics_extensions.cpp @@ -60,6 +60,10 @@ void ConfigureGraphicsExtensions::Setup(const ConfigurationShared::Builder& buil if (setting->Id() == Settings::values.dyna_state.Id()) { widget->slider->setTickInterval(1); widget->slider->setTickPosition(QSlider::TicksAbove); +#ifdef __APPLE__ + widget->setEnabled(false); + widget->setToolTip(tr("Extended Dynamic State is disabled on macOS due to MoltenVK compatibility issues that cause black screens.")); +#endif } } diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index fca4c94893..1137145659 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp @@ -409,6 +409,12 @@ std::unique_ptr InitializeTranslations(QWidget* parent) "their resolution, details and supported controllers and depending on this setting.\n" "Setting to Handheld can help improve performance for low end systems.")); INSERT(Settings, current_user, QString(), QString()); + INSERT(Settings, disable_nca_verification, tr("Disable NCA Verification"), + tr("Disables integrity verification of NCA content archives." + "\nThis may improve loading speed but risks data corruption or invalid files going " + "undetected.\n" + "Is necessary to make games and updates work that needs firmware 20+.")); + INSERT(Settings, hide_nca_verification_popup, QString(), QString()); // Controls diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 2c3c46114d..4604a7b904 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -553,9 +553,6 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) // Gen keys if necessary OnCheckFirmwareDecryption(); - // Check firmware - OnCheckFirmware(); - game_list->LoadCompatibilityList(); // force reload on first load to ensure add-ons get updated game_list->PopulateAsync(UISettings::values.game_dirs, false); @@ -2039,6 +2036,10 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa } } + if (!OnCheckNcaVerification()) { + return false; + } + /** Exec */ const Core::SystemResultStatus result{ system->Load(*render_window, filename.toStdString(), params)}; @@ -3094,34 +3095,7 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::filesystem::path& command, const std::string& arguments, const std::string& categories, const std::string& keywords, const std::string& name) try { -#if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD - std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop"); - std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc); - if (!shortcut_stream.is_open()) { - LOG_ERROR(Frontend, "Failed to create shortcut"); - return false; - } - // TODO: Migrate fmt::print to std::print in futures STD C++ 23. - fmt::print(shortcut_stream, "[Desktop Entry]\n"); - fmt::print(shortcut_stream, "Type=Application\n"); - fmt::print(shortcut_stream, "Version=1.0\n"); - fmt::print(shortcut_stream, "Name={}\n", name); - if (!comment.empty()) { - fmt::print(shortcut_stream, "Comment={}\n", comment); - } - if (std::filesystem::is_regular_file(icon_path)) { - fmt::print(shortcut_stream, "Icon={}\n", icon_path.string()); - } - fmt::print(shortcut_stream, "TryExec={}\n", command.string()); - fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments); - if (!categories.empty()) { - fmt::print(shortcut_stream, "Categories={}\n", categories); - } - if (!keywords.empty()) { - fmt::print(shortcut_stream, "Keywords={}\n", keywords); - } - return true; -#elif defined(_WIN32) // Windows +#ifdef _WIN32 // Windows HRESULT hr = CoInitialize(nullptr); if (FAILED(hr)) { LOG_ERROR(Frontend, "CoInitialize failed"); @@ -3183,7 +3157,34 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path, return false; } return true; -#else // Unsupported platform +#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Any desktop NIX + std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop"); + std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc); + if (!shortcut_stream.is_open()) { + LOG_ERROR(Frontend, "Failed to create shortcut"); + return false; + } + // TODO: Migrate fmt::print to std::print in futures STD C++ 23. + fmt::print(shortcut_stream, "[Desktop Entry]\n"); + fmt::print(shortcut_stream, "Type=Application\n"); + fmt::print(shortcut_stream, "Version=1.0\n"); + fmt::print(shortcut_stream, "Name={}\n", name); + if (!comment.empty()) { + fmt::print(shortcut_stream, "Comment={}\n", comment); + } + if (std::filesystem::is_regular_file(icon_path)) { + fmt::print(shortcut_stream, "Icon={}\n", icon_path.string()); + } + fmt::print(shortcut_stream, "TryExec={}\n", command.string()); + fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments); + if (!categories.empty()) { + fmt::print(shortcut_stream, "Categories={}\n", categories); + } + if (!keywords.empty()) { + fmt::print(shortcut_stream, "Keywords={}\n", keywords); + } + return true; +#else // Unsupported platform return false; #endif } catch (const std::exception& e) { @@ -3228,7 +3229,7 @@ bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_vi #if defined(_WIN32) out_icon_path = Common::FS::GetEdenPath(Common::FS::EdenPath::IconsDir); ico_extension = "ico"; -#elif defined(__linux__) || defined(__FreeBSD__) +#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256"; #endif // Create icons directory if it doesn't exist @@ -4459,7 +4460,6 @@ void GMainWindow::InstallFirmware(const QString& location, bool recursive) { progress.close(); OnCheckFirmwareDecryption(); - OnCheckFirmware(); } void GMainWindow::OnInstallFirmware() { @@ -4580,7 +4580,6 @@ void GMainWindow::OnInstallDecryptionKeys() { } OnCheckFirmwareDecryption(); - OnCheckFirmware(); } void GMainWindow::OnAbout() { @@ -4609,6 +4608,7 @@ void GMainWindow::OnToggleStatusBar() { void GMainWindow::OnGameListRefresh() { // force reload add-ons etc game_list->ForceRefreshGameDirectory(); + SetFirmwareVersion(); } void GMainWindow::OnAlbum() { @@ -4707,13 +4707,42 @@ void GMainWindow::OnOpenControllerMenu() { } void GMainWindow::OnHomeMenu() { + auto result = FirmwareManager::VerifyFirmware(*system.get()); + + switch (result) { + case FirmwareManager::ErrorFirmwareMissing: + QMessageBox::warning(this, tr("No firmware available"), + tr("Please install firmware to use the Home Menu.")); + return; + case FirmwareManager::ErrorFirmwareCorrupted: + QMessageBox::warning(this, tr("Firmware Corrupted"), + tr(FirmwareManager::GetFirmwareCheckString(result))); + return; + case FirmwareManager::ErrorFirmwareTooNew: { + if (!UISettings::values.show_fw_warning.GetValue()) break; + + QMessageBox box(QMessageBox::Warning, + tr("Firmware Too New"), + tr(FirmwareManager::GetFirmwareCheckString(result)) + tr("\nContinue anyways?"), + QMessageBox::Yes | QMessageBox::No, + this); + + QCheckBox *checkbox = new QCheckBox(tr("Don't show again")); + box.setCheckBox(checkbox); + + int button = box.exec(); + if (checkbox->isChecked()) { + UISettings::values.show_fw_warning.SetValue(false); + } + + if (button == static_cast(QMessageBox::No)) return; + break; + } default: + break; + } + constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - QMessageBox::warning(this, tr("No firmware available"), - tr("Please install the firmware to use the Home Menu.")); - return; - } auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); if (!qlaunch_applet_nca) { @@ -4853,7 +4882,7 @@ void GMainWindow::CreateShortcut(const std::string& game_path, const u64 program } } -#if defined(__linux__) +#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Special case for AppImages // Warn once if we are making a shortcut to a volatile AppImage if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) { @@ -4863,7 +4892,7 @@ void GMainWindow::CreateShortcut(const std::string& game_path, const u64 program } UISettings::values.shortcut_already_warned = true; } -#endif // __linux__ +#endif // Create shortcut std::string arguments{arguments_}; @@ -5240,17 +5269,39 @@ void GMainWindow::OnCheckFirmwareDecryption() { UpdateMenuState(); } -void GMainWindow::OnCheckFirmware() { - auto result = FirmwareManager::VerifyFirmware(*system.get()); +bool GMainWindow::OnCheckNcaVerification() { + if (!Settings::values.disable_nca_verification.GetValue()) + return true; - switch (result) { - case FirmwareManager::FirmwareGood: - break; - default: - QMessageBox::warning(this, tr("Firmware Read Error"), - tr(FirmwareManager::GetFirmwareCheckString(result))); - break; + const bool currently_hidden = Settings::values.hide_nca_verification_popup.GetValue(); + LOG_INFO(Frontend, "NCA Verification is disabled. Popup State={}", currently_hidden); + if (currently_hidden) + return true; + + QMessageBox msgbox(this); + msgbox.setWindowTitle(tr("NCA Verification Disabled")); + msgbox.setText(tr("NCA Verification is disabled.\n" + "This is required to run new games and updates.\n" + "Running without verification can cause instability or crashes if NCA files " + "are corrupt, modified, or tampered.\n" + "If unsure, re-enable verification in Eden's Settings and use firmware " + "version 19.0.1 or below.")); + msgbox.setIcon(QMessageBox::Warning); + msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgbox.setDefaultButton(QMessageBox::Ok); + + QCheckBox* cb = new QCheckBox(tr("Don't show again"), &msgbox); + cb->setChecked(currently_hidden); + msgbox.setCheckBox(cb); + + int result = msgbox.exec(); + + const bool hide = cb->isChecked(); + if (hide != currently_hidden) { + Settings::values.hide_nca_verification_popup.SetValue(hide); } + + return result == static_cast(QMessageBox::Ok); } bool GMainWindow::CheckFirmwarePresence() { @@ -5273,7 +5324,7 @@ void GMainWindow::SetFirmwareVersion() { const std::string display_version(firmware_data.display_version.data()); const std::string display_title(firmware_data.display_title.data()); - LOG_INFO(Frontend, "Installed firmware: {}", display_title); + LOG_INFO(Frontend, "Installed firmware: {}", display_version); firmware_label->setText(QString::fromStdString(display_version)); firmware_label->setToolTip(QString::fromStdString(display_title)); @@ -5730,17 +5781,13 @@ int main(int argc, char* argv[]) { #ifdef _WIN32 // Increases the maximum open file limit to 8192 _setmaxstdio(8192); -#endif - -#ifdef __APPLE__ +#elif defined(__APPLE__) // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". // But since we require the working directory to be the executable path for the location of // the user folder in the Qt Frontend, we need to cd into that working directory const auto bin_path = Common::FS::GetBundleDirectory() / ".."; chdir(Common::FS::PathToUTF8String(bin_path).c_str()); -#endif - -#ifdef __linux__ +#elif defined(__unix__) && !defined(__ANDROID__) // Set the DISPLAY variable in order to open web browsers // TODO (lat9nq): Find a better solution for AppImages to start external applications if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) { @@ -5749,7 +5796,7 @@ int main(int argc, char* argv[]) { // Fix the Wayland appId. This needs to match the name of the .desktop file without the .desktop // suffix. - QGuiApplication::setDesktopFileName(QStringLiteral("org.eden_emu.eden")); + QGuiApplication::setDesktopFileName(QStringLiteral("dev.eden_emu.eden")); #endif SetHighDPIAttributes(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 8a34a9f075..7857788fcf 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -424,7 +424,6 @@ private slots: void OnCreateHomeMenuShortcut(GameListShortcutTarget target); void OnCaptureScreenshot(); void OnCheckFirmwareDecryption(); - void OnCheckFirmware(); void OnLanguageChanged(const QString& locale); void OnMouseActivity(); bool OnShutdownBegin(); @@ -486,6 +485,9 @@ private: const std::filesystem::path& command, const std::string& arguments, const std::string& categories, const std::string& keywords, const std::string& name); + + bool OnCheckNcaVerification(); + /** * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog * The only difference is that it returns a boolean. diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 85de0ae72d..3322b31ca3 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -212,6 +212,9 @@ struct Values { // Play time Setting show_play_time{linkage, true, "show_play_time", Category::UiGameList}; + // misc + Setting show_fw_warning{linkage, true, "show_fw_warning", Category::Miscellaneous}; + bool configuration_applied; bool reset_to_defaults; bool shortcut_already_warned{false}; diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index e22cf84bf1..551df7b4cd 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2015 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -138,7 +141,7 @@ bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) icon_file.Close(); return true; -#elif defined(__linux__) || defined(__FreeBSD__) +#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Convert and write the icon as a PNG if (!image.save(QString::fromStdString(icon_path.string()))) { LOG_ERROR(Frontend, "Could not write icon as PNG to file"); diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index ebd8fd7387..a60650bc19 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2018 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later @@ -28,7 +31,7 @@ add_executable(yuzu-cmd yuzu.rc ) -target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common) +target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common video_core) target_link_libraries(yuzu-cmd PRIVATE glad) if (MSVC) target_link_libraries(yuzu-cmd PRIVATE getopt) @@ -38,7 +41,7 @@ target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon") target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR}) -target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2 Vulkan::Headers) +target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2) if(UNIX AND NOT APPLE) install(TARGETS yuzu-cmd) diff --git a/tools/cpm-fetch-all.sh b/tools/cpm-fetch-all.sh index 38f7b1f941..eac0f861a4 100755 --- a/tools/cpm-fetch-all.sh +++ b/tools/cpm-fetch-all.sh @@ -6,5 +6,6 @@ # SPDX-FileCopyrightText: 2025 crueter # SPDX-License-Identifier: GPL-3.0-or-later -LIBS=$(find . externals externals/nx_tzdb src/yuzu/externals externals/ffmpeg src/dynarmic/externals -maxdepth 1 -name cpmfile.json -exec jq -j 'keys_unsorted | join(" ")' {} \; -printf " ") +LIBS=$(find . externals src/yuzu/externals src/dynarmic -maxdepth 2 -name cpmfile.json -exec jq -j 'keys_unsorted | join(" ")' {} \; -printf " ") + tools/cpm-fetch.sh $LIBS \ No newline at end of file diff --git a/tools/cpm-fetch.sh b/tools/cpm-fetch.sh index 1c2ce007d2..f9614b1904 100755 --- a/tools/cpm-fetch.sh +++ b/tools/cpm-fetch.sh @@ -84,7 +84,7 @@ ci_package() { for platform in windows-amd64 windows-arm64 android solaris freebsd linux linux-aarch64; do FILENAME="${NAME}-${platform}-${VERSION}.${EXT}" - DOWNLOAD="https://github.com/${REPO}/releases/download/v${VERSION}/${FILENAME}" + DOWNLOAD="https://$GIT_URL/${REPO}/releases/download/v${VERSION}/${FILENAME}" PACKAGE_NAME="$PACKAGE" KEY=$platform @@ -122,14 +122,32 @@ do URL=$(jq -r ".url" <<< "$JSON") REPO=$(jq -r ".repo" <<< "$JSON") SHA=$(jq -r ".sha" <<< "$JSON") + GIT_URL=$(jq -r ".git_url" <<< "$JSON") + + [ "$GIT_URL" == null ] && GIT_URL=github.com + + VERSION=$(jq -r ".version" <<< "$JSON") + GIT_VERSION=$(jq -r ".git_version" <<< "$JSON") + + if [ "$GIT_VERSION" != null ]; then + VERSION_REPLACE="$GIT_VERSION" + else + VERSION_REPLACE="$VERSION" + fi + + TAG=$(jq -r ".tag" <<< "$JSON") + + TAG=$(sed "s/%VERSION%/$VERSION_REPLACE/" <<< $TAG) + + ARTIFACT=$(jq -r ".artifact" <<< "$JSON") + ARTIFACT=$(sed "s/%VERSION%/$VERSION_REPLACE/" <<< $ARTIFACT) + ARTIFACT=$(sed "s/%TAG%/$TAG/" <<< $ARTIFACT) if [ "$URL" != "null" ]; then DOWNLOAD="$URL" elif [ "$REPO" != "null" ]; then - GIT_URL="https://github.com/$REPO" + GIT_URL="https://$GIT_URL/$REPO" - TAG=$(jq -r ".tag" <<< "$JSON") - ARTIFACT=$(jq -r ".artifact" <<< "$JSON") BRANCH=$(jq -r ".branch" <<< "$JSON") if [ "$TAG" != "null" ]; then @@ -155,24 +173,21 @@ do # key parsing KEY=$(jq -r ".key" <<< "$JSON") - if [ "$KEY" == null ]; then - VERSION=$(jq -r ".version" <<< "$JSON") - GIT_VERSION=$(jq -r ".git_version" <<< "$JSON") - + if [ "$KEY" == null ]; then if [ "$SHA" != null ]; then KEY=$(cut -c1-4 - <<< "$SHA") elif [ "$GIT_VERSION" != null ]; then KEY="$GIT_VERSION" + elif [ "$TAG" != null ]; then + KEY="$TAG" elif [ "$VERSION" != null ]; then KEY="$VERSION" else - echo "No valid key could be determined for $package. Must define one of: key, sha, version, git_version" + echo "No valid key could be determined for $package. Must define one of: key, sha, tag, version, git_version" continue fi fi - echo $KEY - echo "Downloading regular package $package, with key $KEY, from $DOWNLOAD" # hash parsing